Multi-User Collaborative Inventory Management Platform
The application is live and public. You can create inventories and start managing items immediately after registration.
ℹ️ Note
Performance Tip: This project is hosted on a Render Free Tier. If the link takes about 30 seconds to load initially, the server is simply "waking up" from its sleep cycle. Once active, the SignalR connection provides sub-millisecond real-time responses. ⚡
Live Link: https://inventory-manager-reta.onrender.com
A full-featured, multi-user inventory management web application built with ASP.NET Core MVC, PostgreSQL, SignalR, and Bootstrap. Users can create personal or shared inventories, define custom fields per inventory, manage items collaboratively, discuss in real-time, and control access granularly.
- Features
- Tech Stack
- Architecture Overview
- Domain Models
- Project Structure
- Getting Started
- Configuration
- Authentication & Authorization
- Inventory System
- Items
- Real-Time Discussion (SignalR)
- Search
- Admin Panel
- Themes & Localization
- Auto-Save
- Export (CSV & Excel)
- Deployment (Docker / Render)
- Known Issues & Troubleshooting
- Contributing
- License
- Multi-user authentication — register/login with form-based auth or social login (Google, Facebook)
- Email confirmation — optional SMTP-based email verification on registration
- Inventory management — create, edit, delete inventories with title, description, category, cover image, and public/private visibility
- Custom fields — define an arbitrary number of typed fields per inventory (string, integer, boolean, date, select-list)
- Field validation tuning — set min/max length, regex patterns for strings; min/max value ranges for numeric fields
- Custom ID generation — define human-readable auto-incrementing IDs per inventory (e.g.,
BOOK-001,ITEM-042) - Item management — add, edit, delete items within an inventory using the custom schema defined by the owner
- Like system — authenticated users can like/unlike individual items with real-time like counts
- Tagging — tag inventories with freeform tags; autocomplete on input
- Full-text search — search across all public inventories and items by name, description, or tag
- Real-time discussion — per-inventory live chat powered by SignalR with Markdown rendering
- Access control — share inventories with specific users (read-only or read-write)
- Inventory export — export all items to CSV or Excel (.xlsx)
- Statistics — per-inventory statistics tab (item count, like count, top items)
- Document/image previews — render image thumbnails and PDF iframes for URL fields
- Drag-and-drop reordering — reorder custom fields and custom ID elements
- Auto-save — inventory edit page auto-saves every 8 seconds with optimistic locking
- Markdown rendering — inventory descriptions and discussion posts support full Markdown
- Light/Dark theme — per-user theme preference persisted in the database
- Multi-language (i18n) — language preference persisted per user via
RequestCultureMiddleware - Blocked user handling — admins can block users; blocked users are intercepted by middleware
- Profile page — view your own inventories, liked items, and access grants; all tables are sortable and filterable
- User management — view all users, block/unblock, promote/demote admin role
- Full inventory access — admins act as owner of every inventory in the system
| Layer | Technology |
|---|---|
| Framework | ASP.NET Core 8 MVC |
| Language | C# 12 |
| ORM | Entity Framework Core 8 (Npgsql provider) |
| Database | PostgreSQL |
| Real-time | SignalR (ASP.NET Core) |
| Authentication | ASP.NET Core Identity + Google OAuth + Facebook OAuth |
| Frontend | Bootstrap 5, Vanilla JS |
| Markdown | Markdig (server-side), Marked.js (client-side SignalR) |
| CSV Export | CsvHelper |
| Excel Export | ClosedXML |
| Containerization | Docker |
| Hosting | Render.com |
The project follows a clean MVC + Service Layer pattern:
Browser
│
▼
Controllers ──────► Services (business logic)
│ │
│ ▼
│ ApplicationDbContext (EF Core)
│ │
▼ ▼
Views (Razor) PostgreSQL
│
▼
Hubs (SignalR)
Middleware pipeline order:
UseRequestLocalization
→ UseStaticFiles
→ UseRouting
→ UseAuthentication
→ BlockedUserMiddleware
→ RequestCultureMiddleware
→ UseAuthorization
→ MapControllers / MapHub
Extends IdentityUser with:
IsBlocked(bool) — blocked users are redirected by middlewareTheme(string) —"light"or"dark", resolved byThemeHelperLanguage(string) — culture code, e.g."en","de", resolved byLocalizationHelper
Id(Guid),Title,Description,Category,ImageUrlIsPublic(bool) — public inventories are writable by all authenticated usersOwnerId(FK → ApplicationUser,DeleteBehavior.Restrict)- Navigation:
Fields(List<InventoryField>),Items,Tags(via InventoryTag),AccessGrants
Id(Guid),InventoryId(FK),Name,FieldType(enum),Order(int)FieldTypevalues:String,Integer,Boolean,Date,SelectList- Optional
FieldValidationchild:MinLength,MaxLength,RegexPattern,MinValue,MaxValue - Optional
FieldListOptionchildren (for SelectList type): ordered list of allowed values
Id(Guid),InventoryId(FK),Name,ImageUrl- Dynamic field values stored as
string?properties mapped toInventoryFieldrows [Timestamp] Version(byte[]) — rowversion for optimistic concurrencyCustomId(string?) — generated byCustomIdService- Navigation:
Likes(List<ItemLike>),Tags
Id(Guid),ItemId(FK),UserId(FK)- Unique constraint on (ItemId, UserId)
Id(Guid),InventoryId(FK),UserId(FK),CanEdit(bool)
Id(Guid),InventoryId(FK),UserId(FK),Content(Markdown string),CreatedAt
Tag:Id(Guid),Name(unique)InventoryTag: junction table (InventoryId, TagId)
InventorySequence: tracks the current counter value per inventory for custom ID generationIdElement: defines the parts of the custom ID template (prefix, counter, padding)
InventoryManager/
│
├── Controllers/ # HTTP request handlers — thin, delegate to services
│ ├── AccountController.cs # Register, Login, Logout, social login callbacks, email confirmation
│ ├── AdminController.cs # Admin-only: user list, block/unblock, role management
│ ├── DiscussionController.cs # REST fallback for posting discussion messages
│ ├── HomeController.cs # Home page with top inventories, Privacy page
│ ├── InventoryController.cs # Full inventory CRUD, Details tabbed view, AutoSave, CSV/Excel export
│ ├── ItemController.cs # Item CRUD, ToggleLike (JSON), DeleteMultiple (checkbox batch)
│ ├── ProfileController.cs # User profile view, SetTheme POST, SetLanguage POST
│ └── SearchController.cs # Full-text search across inventories and items
│
├── Data/ # EF Core data access layer
│ ├── ApplicationDbContext.cs # DbContext — registers all entity sets and applies configurations
│ └── Configurations/ # Fluent API entity configurations (one file per entity)
│ ├── IdElementConfig.cs # Custom ID element template parts
│ ├── InventoryAccessConfig.cs # Unique constraint on (InventoryId, UserId)
│ ├── InventoryConfig.cs # OwnerId: DeleteBehavior.Restrict (prevents null on user delete)
│ ├── InventoryFieldConfig.cs # Field order index, FK cascade rules
│ ├── InventorySequenceConfig.cs # One sequence row per inventory for ID generation
│ ├── ItemConfig.cs # [Timestamp] Version rowversion for optimistic concurrency
│ ├── ItemLikeConfig.cs # Unique constraint on (ItemId, UserId) — one like per user
│ └── TagConfig.cs # Unique index on Tag.Name
│
├── Extensions/ # Static extension methods for cleaner Program.cs
│ ├── ApplicationBuilderExtensions.cs # app.UseXxx() pipeline helpers
│ ├── IdentityExtensions.cs # Identity setup helpers (roles, seed admin)
│ └── ServiceCollectionExtensions.cs # builder.Services.AddXxx() registration helpers
│
├── Helpers/ # Stateless utility classes
│ ├── IdGeneratorHelper.cs # Renders custom ID string from template + current sequence value
│ ├── LocalizationHelper.cs # Maps language code string → CultureInfo instance
│ ├── MarkdownHelper.cs # Wraps Markdig: ToHtml(string markdown) → safe HTML string
│ ├── PermissionHelper.cs # IsOwner(inventory, userId) with null OwnerId guard
│ └── ThemeHelper.cs # Resolve(string theme) → Bootstrap CSS file path
│
├── Hubs/
│ └── DiscussionHub.cs # SignalR hub: JoinInventory(id), SendMessage(id, content) → broadcasts to group
│
├── Middleware/
│ ├── BlockedUserMiddleware.cs # Intercepts every request — redirects IsBlocked users to /Account/Blocked
│ └── RequestCultureMiddleware.cs # Reads user.Language from DB, sets Thread.CurrentCulture per request
│
├── Migrations/ # EF Core auto-generated migration files
│
├── Models/
│ ├── Domain/ # Core business entities (mapped to DB tables)
│ │ ├── ApplicationUser.cs # Extends IdentityUser — adds IsBlocked, Theme, Language
│ │ ├── DiscussionPost.cs # Per-inventory discussion message with Markdown content
│ │ ├── IdElement.cs # One part of a custom ID template (prefix, counter, padding)
│ │ ├── Inventory.cs # Root aggregate — Title, Description, IsPublic, OwnerId
│ │ ├── InventoryAccess.cs # Access grant record — UserId + CanEdit flag
│ │ ├── InventoryField.cs # Typed custom field definition scoped to an inventory
│ │ ├── InventorySequence.cs # Auto-increment counter per inventory for custom ID generation
│ │ ├── InventoryTag.cs # Junction table: many-to-many Inventory ↔ Tag
│ │ ├── Item.cs # Inventory row — Name, CustomId, field values, [Timestamp] Version
│ │ ├── ItemLike.cs # Like record — ItemId + UserId (unique pair)
│ │ └── Tag.cs # Shared tag pool (unique Name) used across all inventories
│ │
│ ├── DTOs/ # Lightweight transfer objects for service ↔ hub/controller communication
│ │ ├── DiscussionPostDto.cs # Post data sent to SignalR clients (UserId included for profile links)
│ │ ├── IdPreviewDto.cs # Preview of generated custom ID before saving
│ │ └── TagAutocompleteDto.cs # Tag suggestion for autocomplete dropdown
│ │
│ └── ViewModels/ # View-specific data shapes passed from controllers to Razor views
│ ├── AccessManageVM.cs # Access tab: current grants + user search results
│ ├── AdminUserVM.cs # Admin panel: user row with role and blocked status
│ ├── DiscussionTabViewModel.cs # Discussion tab: existing posts + current user info
│ ├── InventoryCreateVM.cs # Create inventory form fields
│ ├── InventoryDetailsViewModel.cs # Full details page: inventory + items + tabs + permission flags
│ ├── InventoryEditVM.cs # Edit form with Version (rowversion) for optimistic locking
│ ├── ItemCreateViewModel.cs # Dynamic item create form bound to inventory's custom fields
│ ├── ItemEditVM.cs # Item edit form with existing field values pre-populated
│ ├── ProfileVM.cs # Profile page: owned inventories, liked items, access grants
│ ├── SearchResultVM.cs # Search results: matched inventories and items
│ └── ErrorViewModel.cs # Default MVC error page model
│
├── Resources/
│ └── SharedResource.cs # Marker class for shared .resx localization strings
│
├── Services/
│ ├── Interfaces/ # Contracts — controllers depend on these, not concrete classes
│ │ ├── IAccessService.cs # CanEditInventory, CanEditItems, GetAccessList
│ │ ├── ICustomIdService.cs # GenerateId, PreviewId, SaveTemplate
│ │ ├── IDiscussionService.cs # GetPostsAsync, AddPostAsync
│ │ ├── IInventoryService.cs # GetAll, GetById, Create, Update, Delete
│ │ ├── IItemService.cs # GetItems, GetById, Create, Update, Delete, ValidateFields
│ │ ├── ISearchService.cs # Search(query, userId) → SearchResultVM
│ │ ├── IStatisticsService.cs # GetStats(inventoryId) → counts and top items
│ │ └── ITagService.cs # GetCloud, Autocomplete, AddTag, RemoveTag
│ │
│ ├── AccessService.cs # Permission checks: owner / admin role / IsPublic / explicit grant
│ ├── CustomIdService.cs # Renders ID template + atomically increments InventorySequence
│ ├── DiscussionService.cs # Persists posts, maps to DiscussionPostDto (includes UserId)
│ ├── InventoryService.cs # Inventory CRUD with tag sync and field management
│ ├── ItemService.cs # Item CRUD with per-field validation against FieldValidation rules
│ ├── SearchService.cs # Queries inventories + items filtered by access and search term
│ ├── StatisticsService.cs # Aggregates item count, like count, top-liked items
│ └── TagService.cs # Tag cloud query, autocomplete endpoint, tag attach/detach
│
├── Views/
│ ├── Account/
│ │ ├── Login.cshtml # Form login + Google/Facebook social login buttons
│ │ └── Register.cshtml # Registration form; redirects to confirmation page if SMTP configured
│ │
│ ├── Admin/
│ │ └── Index.cshtml # User management table: block/unblock, promote/demote
│ │
│ ├── Home/
│ │ ├── Index.cshtml # Landing page with top-5 most liked public inventories
│ │ └── Privacy.cshtml # Privacy policy page
│ │
│ ├── Inventory/
│ │ ├── _AccessTab.cshtml # Manage per-user access grants (search + grant/revoke)
│ │ ├── _CustomIdTab.cshtml # Custom ID template builder with live preview
│ │ ├── _DiscussionTab.cshtml # Real-time chat; Markdown-rendered posts; username → profile link
│ │ ├── _FieldsTab.cshtml # Dynamic field editor: add/remove fields, set type, tuning options
│ │ ├── _ItemsTab.cshtml # Items table with checkboxes + Delete Selected toolbar (no row buttons)
│ │ ├── _SettingsTab.cshtml # Public/Private toggle, rename, danger zone (delete inventory)
│ │ ├── _StatisticsTab.cshtml # Item count, like count, top items charts
│ │ ├── Create.cshtml # New inventory form
│ │ ├── Details.cshtml # Tabbed detail page; loads SignalR + like.js + autosave scripts
│ │ ├── Edit.cshtml # Edit form with hidden Version field for optimistic concurrency
│ │ └── Index.cshtml # User's inventory list with search/filter
│ │
│ ├── Item/
│ │ ├── Create.cshtml # Dynamic item form generated from inventory's custom fields
│ │ └── Edit.cshtml # Pre-populated edit form with existing field values
│ │
│ ├── Profile/
│ │ └── Index.cshtml # Owned inventories, liked items, access grants — all sortable/filterable
│ │
│ ├── Search/
│ │ └── Results.cshtml # Search results grouped by inventories and items
│ │
│ └── Shared/
│ ├── Components/
│ │ ├── InventoryTableViewComponent.cs # Reusable inventory table rendered as a View Component
│ │ └── TagCloudViewComponent.cs # Tag cloud widget used on Home and Profile pages
│ ├── _Layout.cshtml # Master layout: injects theme CSS, navbar, login partial
│ ├── _LoginPartial.cshtml # Login/logout links in navbar
│ ├── _Navbar.cshtml # Top navigation bar with search box
│ ├── _SearchBar.cshtml # Reusable search input partial
│ ├── _ValidationScriptsPartial.cshtml # jQuery Validation scripts included on form pages
│ └── Error.cshtml # Generic error page
│
├── wwwroot/ # Static assets served directly by the web server
│ ├── js/
│ │ ├── discussion-signalr.js # SignalR client: connects, joins group, renders Markdown via Marked.js
│ │ ├── like.js # Sends fetch POST to /Item/ToggleLike; updates like count in DOM
│ │ ├── inventory-autosave.js # Debounced 8s auto-save; handles optimistic lock Version field
│ │ ├── field-validation.js # Client-side field tuning validation (min/max/regex) before submit
│ │ └── preview.js # Detects image/PDF URLs in item fields; renders thumbnails + lightbox
│ └── css/
│
├── .gitignore
├── appsettings.json # Safe defaults — all secrets are empty strings
├── appsettings.Development.json # Local overrides (do not commit secrets)
├── build_result.txt # CI build output log
├── db_update_log.txt # Migration apply history log
├── Dockerfile # Multi-stage build: sdk → aspnet runtime image
├── Program.cs # App entry point: service registration, middleware pipeline, seed
└── README.md
- .NET 8 SDK
- PostgreSQL 14+
- Node.js (optional, only if you modify frontend assets)
- Docker (optional, for containerized deployment)
# 1. Clone the repository
git clone https://github.com/shahedul-islam-joshi/InventoryManager.git
cd InventoryManager
# 2. Restore NuGet packages
dotnet restore
# 3. Set up user secrets for sensitive config
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Host=localhost;Port=5432;Database=inventorymanager;Username=postgres;Password=yourpassword"# Apply all migrations (creates tables and seeds the admin user)
dotnet ef database updateThe seed creates a default admin account:
- Email:
admin@admin.com - Password:
admin123
Important: Change the admin password immediately after first login in a production environment.
dotnet runNavigate to https://localhost:7016 (or the port shown in your terminal).
All sensitive configuration should be provided via user secrets (local) or environment variables (production). Never commit secrets to source control.
appsettings.json contains safe defaults with empty values for all secrets:
{
"ConnectionStrings": {
"DefaultConnection": ""
},
"Authentication": {
"Google": { "ClientId": "", "ClientSecret": "" },
"Facebook": { "AppId": "", "AppSecret": "" }
},
"Email": {
"Host": "",
"Port": "587",
"Username": "",
"Password": "",
"From": ""
}
}All optional integrations (Google, Facebook, SMTP email) are conditionally registered — if the values are empty the app starts normally without those features, so you can run the app locally with just a database connection string.
Google:
- Go to Google Cloud Console
- Create a project → APIs & Services → Credentials → Create OAuth 2.0 Client ID
- Set Authorized redirect URI to:
https://yourdomain.com/signin-google - Copy the Client ID and Client Secret
Facebook:
- Go to Meta for Developers
- Create App → Add Facebook Login product
- Set Valid OAuth Redirect URI to:
https://yourdomain.com/signin-facebook - Copy App ID and App Secret
Store credentials:
dotnet user-secrets set "Authentication:Google:ClientId" "your-client-id"
dotnet user-secrets set "Authentication:Google:ClientSecret" "your-client-secret"
dotnet user-secrets set "Authentication:Facebook:AppId" "your-app-id"
dotnet user-secrets set "Authentication:Facebook:AppSecret" "your-app-secret"Email confirmation is sent on registration when SMTP credentials are configured. If not configured, users can log in without confirming their email.
dotnet user-secrets set "Email:Host" "smtp.gmail.com"
dotnet user-secrets set "Email:Port" "587"
dotnet user-secrets set "Email:Username" "you@gmail.com"
dotnet user-secrets set "Email:Password" "your-app-password"
dotnet user-secrets set "Email:From" "you@gmail.com"For Gmail, use an App Password rather than your main password.
The app uses ASP.NET Core Identity for user management with the following roles:
| Role | Permissions |
|---|---|
Admin |
Full access to every inventory, user management panel, block/unblock users |
| Authenticated User | Create inventories, access inventories they own or have been granted access to |
| Anonymous | Browse public inventories and items (read-only) |
Middleware chain for authorization:
BlockedUserMiddleware— intercepts all requests from blocked users and redirects to/Account/BlockedRequestCultureMiddleware— readsuser.Languagefrom the database and setsThread.CurrentCulture- ASP.NET Core
[Authorize]attributes on controllers and actions
Access rules for inventory actions:
- Owner → full access (edit, delete, manage fields, manage access list)
- Admin → acts as owner of every inventory
- Explicit access grant with
CanEdit = true→ can add/edit/delete items IsPublic = true+ authenticated → can add/edit/delete items- Explicit access grant with
CanEdit = false→ read-only - Anonymous + public → read-only
Each inventory can define an unlimited number of typed fields. The inventory owner configures fields in the Fields tab of the inventory detail page.
Supported field types:
| Type | Description | Stored As |
|---|---|---|
String |
Free-text input | varchar |
Integer |
Whole number input | string (parsed on display) |
Boolean |
Checkbox | "true"/"false" |
Date |
Date picker | ISO 8601 string |
SelectList |
Dropdown from predefined options | selected option value |
Field validation tuning (optional per field):
- String fields: MinLength, MaxLength, RegexPattern
- Numeric fields: MinValue, MaxValue
- Validated both server-side (in
ItemService) and client-side (field-validation.js)
SelectList options: When a field type is SelectList, the owner can define an ordered list of allowed values (e.g., Desktop, Laptop, Tablet). Items then show a <select> dropdown restricted to those values.
Inventory owners can define a custom ID template for items. This is configured in the Custom ID tab.
Example template: BOOK-{counter:4} → generates BOOK-0001, BOOK-0002, etc.
The InventorySequence table tracks the current counter per inventory. The CustomIdService increments the sequence and renders the template. The IdElement table stores the parts of the template.
Managed in the Access tab of the inventory detail page.
- The owner can search for users by email and grant/revoke access
- Access grants are stored in
InventoryAccesswith aCanEditboolean - The access list is sortable by name and email
Items are the rows of an inventory. Each item has:
- A Name (required)
- An optional Image URL
- One value per custom field defined by the inventory
- An auto-generated Custom ID (if configured)
- A Like count
- Tags (shared tag pool with autocomplete)
The like button is rendered in the item detail view and the items table. Clicking it sends a JSON POST to /Item/ToggleLike and updates the count without page reload.
Implementation notes:
ToggleLikenever loads theItementity (avoids rowversion concurrency conflicts)- Uses
AnyAsyncto verify item existence, then works directly onItemLikestable - No
[ValidateAntiForgeryToken](incompatible with[FromBody]JSON)
Tags are shared across the entire application (not per-inventory). When adding a tag to an item or inventory, the input shows autocomplete suggestions from existing tags via /Tag/Autocomplete?q=....
When an item field value is a URL, the display logic detects the file extension:
.jpg,.jpeg,.png,.gif,.webp→ renders as<img>thumbnail with click-to-enlarge lightbox (Bootstrap modal, no external library).pdf→ renders as<iframe>with fallback link- Other URLs → plain hyperlink text
Each inventory has a live discussion thread powered by ASP.NET Core SignalR.
How it works:
- When the user opens the Details page,
discussion-signalr.jsconnects to/hubs/discussion - The client calls
JoinInventory(inventoryId)to subscribe to that inventory's group - When a message is submitted, it is sent via
SendMessage(inventoryId, content) - The hub calls
AddPostAsynconDiscussionServiceto persist the message - The hub broadcasts
ReceiveMessage(postDto)to all clients in the group - The client renders the message with Marked.js for Markdown and wraps the username in a profile link
Hub: Hubs/DiscussionHub.cs
Client: wwwroot/js/discussion-signalr.js
CDN script (no integrity hash):
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"
crossorigin="anonymous" referrerpolicy="no-referrer"></script>Note: Do not add an
integrityattribute to this script tag. The SHA-512 hash provided by cdnjs does not match the file served and causes the browser to block the script.
Full-text search is available at /Search/Results?q=... and via the navbar search box.
The SearchService queries across:
- Inventory titles and descriptions (public inventories only, or those the user has access to)
- Item names
- Tags
Results are grouped by type and rendered in Views/Search/Results.cshtml.
Accessible at /Admin — only visible and accessible to users in the Admin role.
Features:
- View all registered users with registration date, email, role, and blocked status
- Block / Unblock users (blocked users are immediately redirected on next request)
- Promote users to Admin / Demote from Admin role
- Admins see and can edit every inventory in the system as if they were the owner
Users can switch between Light and Dark themes from their profile page. The selection is persisted in ApplicationUser.Theme.
ThemeHelper.Resolve(theme) returns the correct Bootstrap CSS path. _Layout.cshtml injects the resolved CSS for the logged-in user on every page.
POST endpoint: POST /Profile/SetTheme with body { theme: "dark" }
Users can set their preferred language from their profile page. The selection is persisted in ApplicationUser.Language.
RequestCultureMiddleware reads this value on each request and calls CultureInfo.CurrentCulture and CultureInfo.CurrentUICulture accordingly. Program.cs configures UseRequestLocalization with supported cultures.
POST endpoint: POST /Profile/SetLanguage with body { language: "de" }
The inventory Edit page auto-saves changes every 8 seconds after any field modification (debounced).
Implementation (wwwroot/js/inventory-autosave.js):
- Watches Title, Description, Category, ImageUrl, IsPublic fields for changes
- Debounces — resets the 8-second timer on each keystroke
- POSTs to
/Inventory/AutoSavewith the form data including the currentVersion(rowversion byte array) for optimistic concurrency - Status indicator shows
Saving...,Saved ✓, orConflict — please reload - On success, updates the hidden
Versionfield with the new version returned from the server - On 409 Conflict, stops auto-saving and displays the conflict message so the user can manually resolve
From the inventory detail page, users with at least read access can export all items:
- Export CSV →
GET /Inventory/ExportCsv/{id}→ downloadsinventory-name.csv - Export Excel →
GET /Inventory/ExportExcel/{id}→ downloadsinventory-name.xlsx
Both formats include: Item Name, Custom ID, Tags, all custom field values, Like count, Created date.
Libraries used:
A Dockerfile is included in the repository root.
# Build the image
docker build -t inventorymanager .
# Run with environment variables
docker run -p 8080:8080 \
-e ConnectionStrings__DefaultConnection="Host=db;Database=inventorymanager;Username=postgres;Password=secret" \
inventorymanagerThe live demo is hosted on Render using:
- Web Service running the Docker image
- PostgreSQL managed database (Render-hosted)
Environment variables are set in the Render dashboard under the service's Environment tab. Use the same key names as appsettings.json with __ as the hierarchy separator (e.g., ConnectionStrings__DefaultConnection).
First-deploy checklist:
- Set
ConnectionStrings__DefaultConnectionto your Render PostgreSQL internal URL - Run
dotnet ef database updateagainst the production database (or setAutoMigrate = trueinProgram.cs) - Set
Authentication__Google__ClientIdetc. if social login is needed - Change the seeded admin password immediately
App crashes on startup: ArgumentException: The value cannot be an empty string (Parameter 'ClientId')
Cause: Google or Facebook authentication credentials are empty strings in appsettings.json.
Fix: Social login registration is conditional — it only registers if both ClientId and ClientSecret are non-empty. If you see this error, ensure your Program.cs uses the conditional registration pattern:
var googleClientId = builder.Configuration["Authentication:Google:ClientId"];
if (!string.IsNullOrEmpty(googleClientId))
authBuilder.AddGoogle(options => { ... });Cause: An integrity hash attribute on the SignalR CDN script tag is mismatched.
Fix: Remove the integrity attribute entirely from the SignalR <script> tag in Details.cshtml. It should look exactly like:
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"
crossorigin="anonymous" referrerpolicy="no-referrer"></script>Cause: Loading the Item entity in ToggleLike causes EF Core to track its [Timestamp] Version rowversion. When only ItemLikes rows are modified, EF sees 0 rows affected for Item and throws.
Fix: Never load the Item entity in ToggleLike. Use AnyAsync to check existence, then only operate on the ItemLikes DbSet.
Cause: OnDelete(DeleteBehavior.SetNull) in InventoryConfig nullifies OwnerId when the owner user is deleted.
Fix: Migration RestrictInventoryOwnerDelete changes this to DeleteBehavior.Restrict. If you have existing NULL values, run:
UPDATE "Inventories"
SET "OwnerId" = (SELECT "Id" FROM "AspNetUsers" WHERE "Email" = 'admin@admin.com')
WHERE "OwnerId" IS NULL;Cause: RequestCultureMiddleware is not registered in the pipeline, or UseRequestLocalization is called without options.
Fix: Ensure Program.cs calls app.UseRequestLocalization(locOptions) with a configured RequestLocalizationOptions object, and that app.UseMiddleware<RequestCultureMiddleware>() appears after UseAuthentication and BlockedUserMiddleware.
Pull requests are welcome. For major changes please open an issue first to discuss what you would like to change.
Code style:
- Follow existing C# naming conventions (PascalCase for public members, camelCase for locals)
- Keep controllers thin — business logic belongs in services
- Add XML doc comments on public service interfaces
- Do not commit secrets or connection strings
Before submitting a PR:
dotnet build
dotnet test # if tests are addedThis project is licensed under the MIT License. See LICENSE for details.