ASP.NET Core MVC REST API with SQL Server, Entity Framework & Image Upload
In this updated tutorial, we’ll build a complete REST API using ASP.NET Core MVC with SQL Server and Entity Framework Core, including image upload functionality. The model uses lowercase fields: first_name
, last_name
, email
, and password
to ensure compatibility with Angular JSON structure.
π§° Step 1: Create ASP.NET Core Web API Project
dotnet new webapi -n DotNetRestApi
π Recommended Project Structure
DotNetRestApi/
βββ Controllers/
β βββ UsersController.cs
βββ Models/
β βββ User.cs
UserDto.cs
βββ Data/
β βββ ApplicationDbContext.cs
βββ wwwroot/
β βββ uploads/
βββ appsettings.json
βββ Program.cs
βββ ...
π¦ Step 2: Install EF Core Packages
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
π§Ύ Step 3: Create User Model
namespace DotNetRestApi.Models;
public class User
{
public int Id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public string password { get; set; }
public string? image_url { get; set; }
}
π§Ύ Step 3: Create User DTO
namespace DotNetRestApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace DotNetRestApi.Models
{
public class UserDto
{
[FromForm(Name = "first_name")]
public string FirstName { get; set; }
[FromForm(Name = "last_name")]
public string LastName { get; set; }
[FromForm(Name = "email")]
public string Email { get; set; }
[FromForm(Name = "password")]
public string Password { get; set; }
[FromForm(Name = "image")]
public IFormFile Image { get; set; }
}
}
ποΈ Step 4: Create Database Context
using Microsoft.EntityFrameworkCore; // For DbContext, DbSet
using DotNetRestApi.Models; // For User model
namespace DotNetRestApi.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet Users { get; set; }
}
}
βοΈ Step 5: Add Connection String
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=RestApi;Trusted_Connection=True;TrustServerCertificate=True;"
}
,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
βοΈ Update Program.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using DotNetRestApi.Data;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
// Services
builder.Services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver
{
NamingStrategy = new Newtonsoft.Json.Serialization.SnakeCaseNamingStrategy()
};
});
builder.Services.AddEndpointsApiExplorer(); // For Swagger
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "DotNetRestApi", Version = "v1" });
});
builder.Services.AddDbContext(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
// Middleware
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "DotNetRestApi v1");
});
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
// Your custom snake_case naming policy:
public class SnakeCaseNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name)
{
if (string.IsNullOrEmpty(name))
return name;
var stringBuilder = new System.Text.StringBuilder();
for (int i = 0; i < name.Length; i++) { var c = name[i]; if (char.IsUpper(c)) { if (i > 0)
stringBuilder.Append('_');
stringBuilder.Append(char.ToLower(c));
}
else
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString();
}
}
π§Ύ Step 6: Create UsersController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using DotNetRestApi.Data;
using DotNetRestApi.Models;
namespace DotNetRestApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly ApplicationDbContext _context;
public UsersController(ApplicationDbContext context)
{
_context = context;
}
// GET: api/Users
[HttpGet]
public async Task<ActionResult<IEnumerable>> GetUsers()
{
return await _context.Users.ToListAsync();
}
// GET: api/Users/5
[HttpGet("{id}")]
public async Task<ActionResult> GetUser(int id)
{
var user = await _context.Users.FindAsync(id);
if (user == null) return NotFound();
return user;
}
// POST: api/Users
[HttpPost]
public async Task<ActionResult> CreateUser([FromForm] UserDto userDto)
{
string? imagePath = await SaveImage(userDto.Image);
var user = new Users
{
FirstName = userDto.FirstName,
LastName = userDto.LastName,
Email = userDto.Email,
Password = userDto.Password,
ImageUrl = imagePath // store image path in DB
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
// PUT: api/Users/5
[HttpPut("{id}")]
public async Task UpdateUser(int id, Users user)
{
if (id != user.Id)
return BadRequest();
_context.Entry(user).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Users.Any(u => u.Id == id))
return NotFound();
else
throw;
}
return NoContent();
}
// DELETE: api/Users/5
[HttpDelete("{id}")]
public async Task DeleteUser(int id)
{
var user = await _context.Users.FindAsync(id);
if (user == null) return NotFound();
_context.Users.Remove(user);
await _context.SaveChangesAsync();
return NoContent();
}
// Save uploaded image and return the public path
private async Task<string?> SaveImage(IFormFile image)
{
if (image == null || image.Length == 0)
return null;
var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads");
if (!Directory.Exists(uploadsFolder))
Directory.CreateDirectory(uploadsFolder);
var uniqueFileName = Guid.NewGuid().ToString() + Path.GetExtension(image.FileName);
var filePath = Path.Combine(uploadsFolder, uniqueFileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await image.CopyToAsync(stream);
}
return "/uploads/" + uniqueFileName;
}
}
}
π§± Step 7: Run Migrations
dotnet tool install --global dotnet-ef
dotnet ef migrations add InitialCreate
dotnet ef database update
π‘ Step 8: Test API
- GET
/api/users
β All users - GET
/api/users/{id}
β Single user - POST
/api/users
β Create with image - PUT
/api/users/{id}
β Update with optional image - DELETE
/api/users/{id}
β Delete
β Frontend Form (Angular or Postman)
Send as multipart/form-data
with:
first_name
last_name
email
password
image
(optional)
π― Done!
You now have a complete ASP.NET Core Web API with SQL Server, Entity Framework, and image upload supportβready to connect with Angular or React!