DEV Community

mass status
mass status

Posted on

# ASP.NET Core Web API — Full CRUD + Image Upload + Swagger (Full-Stack Backend Guide)

Intha guide-la Swashbuckle (Swagger), Image Upload, mariyum Full CRUD (Create, Read, Update, Delete) ellam step-by-step cover pannirukom. Update/Delete panrapo pazhaya image-a server-la irundhu automatic-a delete panra logic-um irukku — real-time projects-ku romba mukkiyam.


📁 Final Project Folder Structure

MyCrudApp/
│
├── Controllers/
│   └── ProductController.cs
│
├── Data/
│   └── AppDbContext.cs
│
├── Models/
│   ├── Product.cs
│   └── ProductDto.cs
│
├── Migrations/              👈 (dotnet ef migrations add ku apram auto-generate aagum)
│
├── wwwroot/
│   └── uploads/              👈 (uploaded images ithula store aagum)
│
├── appsettings.json
├── appsettings.Development.json
├── Program.cs
└── MyCrudApp.csproj
Enter fullscreen mode Exit fullscreen mode

Step 1: Project & Packages Setup

dotnet new webapi -n MyCrudApp
cd MyCrudApp
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Swashbuckle.AspNetCore
Enter fullscreen mode Exit fullscreen mode

Design package illama, dotnet ef migrations add command work aagathu — idhu miss pannama add pannunga.


Step 2: Models (Models/Product.cs)

namespace MyCrudApp.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
        public int StockQuantity { get; set; }
        public string ImageUrl { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Models/ProductDto.cs (Request Model with File)

using Microsoft.AspNetCore.Http;

namespace MyCrudApp.Models
{
    public class ProductDto
    {
        public string Name { get; set; }
        public string Description { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
        public int StockQuantity { get; set; }
        public IFormFile? ImageFile { get; set; } // '?' na Optional (updates ku)
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Data/AppDbContext.cs (Missing piece — idhu illama app run aagathu)

using Microsoft.EntityFrameworkCore;
using MyCrudApp.Models;

namespace MyCrudApp.Data
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        {
        }

        public DbSet<Product> Products { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: appsettings.json (Connection String add pannunga)

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=MyCrudAppDb;Trusted_Connection=True;TrustServerCertificate=True;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
Enter fullscreen mode Exit fullscreen mode

Local-la SQL Server illana, Server=localhost ku pathila unga SQL Server instance name podunga (e.g. Server=.\\SQLEXPRESS). SQL auth use panra pakshathula User Id=sa;Password=YourPassword; nu maathi podunga.


Step 5: Program.cs (Swagger & Static Files Setup)

using Microsoft.EntityFrameworkCore;
using MyCrudApp.Data;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();

// Swagger (Swashbuckle) Setup
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Swagger UI Enable panrom
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseStaticFiles(); // Image url browser-la view panna must
app.UseAuthorization();
app.MapControllers();
app.Run();
Enter fullscreen mode Exit fullscreen mode

Step 6: The Ultimate CRUD Controller (Controllers/ProductController.cs)

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyCrudApp.Data;
using MyCrudApp.Models;

namespace MyCrudApp.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        private readonly AppDbContext _context;
        private readonly IWebHostEnvironment _env;

        public ProductController(AppDbContext context, IWebHostEnvironment env)
        {
            _context = context;
            _env = env;
        }

        // 1. CREATE (POST)
        [HttpPost]
        public async Task<IActionResult> CreateProduct([FromForm] ProductDto dto)
        {
            string imageUrl = "";
            if (dto.ImageFile != null && dto.ImageFile.Length > 0)
            {
                imageUrl = await SaveImage(dto.ImageFile);
            }

            var product = new Product
            {
                Name = dto.Name,
                Description = dto.Description,
                Category = dto.Category,
                Price = dto.Price,
                StockQuantity = dto.StockQuantity,
                ImageUrl = imageUrl
            };

            _context.Products.Add(product);
            await _context.SaveChangesAsync();
            return Ok(product);
        }

        // 2. READ ALL (GET)
        [HttpGet]
        public async Task<IActionResult> GetProducts()
        {
            return Ok(await _context.Products.ToListAsync());
        }

        // 2.1 READ BY ID (GET)
        [HttpGet("{id}")]
        public async Task<IActionResult> GetProduct(int id)
        {
            var product = await _context.Products.FindAsync(id);
            if (product == null) return NotFound();
            return Ok(product);
        }

        // 3. UPDATE (PUT)
        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateProduct(int id, [FromForm] ProductDto dto)
        {
            var product = await _context.Products.FindAsync(id);
            if (product == null) return NotFound();

            // Puthu image anuppi irundha, pazhaya image-a delete panni puthusa save pannanum
            if (dto.ImageFile != null && dto.ImageFile.Length > 0)
            {
                DeleteImage(product.ImageUrl);
                product.ImageUrl = await SaveImage(dto.ImageFile);
            }

            product.Name = dto.Name;
            product.Description = dto.Description;
            product.Category = dto.Category;
            product.Price = dto.Price;
            product.StockQuantity = dto.StockQuantity;

            await _context.SaveChangesAsync();
            return Ok(product);
        }

        // 4. DELETE (DELETE)
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteProduct(int id)
        {
            var product = await _context.Products.FindAsync(id);
            if (product == null) return NotFound();

            // Database-la data delete aaga munnadi, server folder-la irundhu image-a delete panrom
            DeleteImage(product.ImageUrl);

            _context.Products.Remove(product);
            await _context.SaveChangesAsync();
            return Ok(new { message = "Product and Image deleted successfully!" });
        }

        // --- Helper Methods ---
        private async Task<string> SaveImage(IFormFile file)
        {
            var uploadsFolder = Path.Combine(_env.WebRootPath, "uploads");
            if (!Directory.Exists(uploadsFolder)) Directory.CreateDirectory(uploadsFolder);

            var uniqueFileName = Guid.NewGuid().ToString() + "_" + file.FileName;
            var filePath = Path.Combine(uploadsFolder, uniqueFileName);

            using (var stream = new FileStream(filePath, FileMode.Create))
            {
                await file.CopyToAsync(stream);
            }
            return $"{Request.Scheme}://{Request.Host}/uploads/{uniqueFileName}";
        }

        private void DeleteImage(string imageUrl)
        {
            if (string.IsNullOrEmpty(imageUrl)) return;
            var fileName = Path.GetFileName(new Uri(imageUrl).LocalPath);
            var filePath = Path.Combine(_env.WebRootPath, "uploads", fileName);
            if (System.IO.File.Exists(filePath))
            {
                System.IO.File.Delete(filePath);
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 7: Database Create Panra Steps (Migration)

Controller and DbContext ready aana apparam, database-a physically create pannanum:

dotnet ef migrations add InitialCreate
dotnet ef database update
Enter fullscreen mode Exit fullscreen mode

Idhu run panna apparam, Products table unga SQL Server database-la automatic-a create aagidum.

wwwroot/uploads folder-a manually create panni vachikonga (illana git-la empty folder track aagathu — venumna .gitkeep file podunga).


Step 8: Swagger-la Test Panra Steps

App-a run pannitu (dotnet run), browser-la http://localhost:<port>/swagger open pannunga.

CREATE (POST)
POST /api/Product → Try it out → Fields fill pannunga → Image choose pannunga → Execute. Data save aagum.

READ (GET)
GET /api/Product → Try it out → Execute. Save aana ella data-vum JSON format-la varum. Athula irukka id-a note panni vachikonga.

UPDATE (PUT)
PUT /api/Product/{id} → Try it out → Note panna id-a mela enter pannunga. Kela form-la details-a maathi (e.g., Price increase pannunga), vera puthu image select pannitu → Execute. Ippo paathingana pazhaya image unga local folder-la irundhu automatic-a delete aagi puthu image vanthurukum!

DELETE (DELETE)
DELETE /api/Product/{id} → Try it out → id enter panni Execute. Database-la irundhu data-vum poidum, unga wwwroot/uploads folder-la irundhu antha specific image-um delete aagidum.


🧠 Quick Recap (Article-ku Highlights)

  • ✅ Swagger UI auto-generated API docs
  • IFormFile mூlam multipart/form-data image upload
  • ✅ EF Core Code-First approach with Migrations
  • ✅ Old image cleanup logic on Update & Delete
  • ✅ Clean separation: Models / DTOs / Data / Controllers

Intha structure follow pannina, unga dev.to article production-ready backend guide-a maarum. All the best! 🚀

Top comments (0)