From 703c08022bae0357fe2eff512367aa00b7478114 Mon Sep 17 00:00:00 2001 From: Bangara Raju Kottedi Date: Mon, 19 Jan 2026 15:48:56 +0530 Subject: [PATCH] Add project files. --- .../SampleApplication.Tests.csproj | 25 +++++ SampleApplication.Tests/ShapeProcessTest.cs | 101 +++++++++++++++++ SampleApplication.slnx | 4 + .../Controllers/ShapeController.cs | 77 +++++++++++++ SampleApplication/Entities/ShapeInput.cs | 13 +++ .../20260119092501_InitialCreate.Designer.cs | 49 +++++++++ .../20260119092501_InitialCreate.cs | 35 ++++++ .../SampleDbContextModelSnapshot.cs | 46 ++++++++ SampleApplication/Program.cs | 32 ++++++ .../Properties/launchSettings.json | 23 ++++ SampleApplication/SampleApplication.csproj | 20 ++++ SampleApplication/SampleApplication.http | 6 ++ SampleApplication/SampleDbContext.cs | 13 +++ SampleApplication/ShapeProcessor.cs | 102 ++++++++++++++++++ SampleApplication/ShapeType.cs | 9 ++ .../appsettings.Development.json | 11 ++ SampleApplication/appsettings.json | 9 ++ 17 files changed, 575 insertions(+) create mode 100644 SampleApplication.Tests/SampleApplication.Tests.csproj create mode 100644 SampleApplication.Tests/ShapeProcessTest.cs create mode 100644 SampleApplication.slnx create mode 100644 SampleApplication/Controllers/ShapeController.cs create mode 100644 SampleApplication/Entities/ShapeInput.cs create mode 100644 SampleApplication/Migrations/20260119092501_InitialCreate.Designer.cs create mode 100644 SampleApplication/Migrations/20260119092501_InitialCreate.cs create mode 100644 SampleApplication/Migrations/SampleDbContextModelSnapshot.cs create mode 100644 SampleApplication/Program.cs create mode 100644 SampleApplication/Properties/launchSettings.json create mode 100644 SampleApplication/SampleApplication.csproj create mode 100644 SampleApplication/SampleApplication.http create mode 100644 SampleApplication/SampleDbContext.cs create mode 100644 SampleApplication/ShapeProcessor.cs create mode 100644 SampleApplication/ShapeType.cs create mode 100644 SampleApplication/appsettings.Development.json create mode 100644 SampleApplication/appsettings.json diff --git a/SampleApplication.Tests/SampleApplication.Tests.csproj b/SampleApplication.Tests/SampleApplication.Tests.csproj new file mode 100644 index 0000000..a3e0643 --- /dev/null +++ b/SampleApplication.Tests/SampleApplication.Tests.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SampleApplication.Tests/ShapeProcessTest.cs b/SampleApplication.Tests/ShapeProcessTest.cs new file mode 100644 index 0000000..d52b446 --- /dev/null +++ b/SampleApplication.Tests/ShapeProcessTest.cs @@ -0,0 +1,101 @@ +using SampleApplication.Entities; + +namespace SampleApplication.Tests +{ + public class ShapeProcessTest + { + [Fact] + public void Rectangle_Calculation_IsCorrect() + { + var processor = new SampleApplication.ShapeProcessor(); + + var rectangle = new ShapeInput + { + Id = 1, + Type = ShapeType.Rectangle, + Dimensions = new[] { 10.0, 5.0 } + }; + + processor.AddShapes(new[] { rectangle }); + + var (area, perimeter) = processor.GetResult(rectangle.Id); + + Assert.Equal(50.0, area, 2); + Assert.Equal(30.0, perimeter, 2); + } + + [Fact] + public void Circle_Calculation_IsCorrect() + { + var processor = new SampleApplication.ShapeProcessor(); + + var circle = new ShapeInput + { + Id = 1, + Type = ShapeType.Circle, + Dimensions = new[] { 7.0 } + }; + + processor.AddShapes(new[] { circle }); + + var (area, perimeter) = processor.GetResult(circle.Id); + + Assert.Equal(153.94, area, 2); + Assert.Equal(43.98, perimeter, 2); + } + + [Fact] + public void Triangle_Calculation_IsCorrect() + { + var processor = new SampleApplication.ShapeProcessor(); + + var triangle = new ShapeInput + { + Id = 1, + Type = ShapeType.Triangle, + Dimensions = new[] { 3.0, 4.0 } + }; + + processor.AddShapes(new[] { triangle }); + + var (area, perimeter) = processor.GetResult(triangle.Id); + + Assert.Equal(6.00, area, 2); + Assert.Equal(12.00, perimeter, 2); + } + + [Fact] + public void Invalid_Dimensions_Throws() + { + var processor = new SampleApplication.ShapeProcessor(); + + Assert.Throws(() => + processor.AddShapes(new[] + { + new ShapeInput + { + Id = 1, + Type = ShapeType.Rectangle, + Dimensions = new[] { -1.0, 2.0 } + } + }) + ); + } + + [Fact] + public void Statistics_AreCorrect() + { + var processor = new SampleApplication.ShapeProcessor(); + var shapes = new[] + { + new ShapeInput { Id = 1, Type = ShapeType.Rectangle, Dimensions = new[] { 2.0, 3.0 } }, + new ShapeInput { Id = 2, Type = ShapeType.Circle, Dimensions = new[] { 3.0 } } + }; + processor.AddShapes(shapes); + var (totalShapes, totalArea, maxPerimeterId) = processor.GetStatistics(); + Assert.Equal(2, totalShapes); + Assert.Equal(34.27,totalArea, 2); + Assert.Equal(2, maxPerimeterId); + } + } +} diff --git a/SampleApplication.slnx b/SampleApplication.slnx new file mode 100644 index 0000000..1fdad95 --- /dev/null +++ b/SampleApplication.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/SampleApplication/Controllers/ShapeController.cs b/SampleApplication/Controllers/ShapeController.cs new file mode 100644 index 0000000..27b5f30 --- /dev/null +++ b/SampleApplication/Controllers/ShapeController.cs @@ -0,0 +1,77 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using SampleApplication.Entities; + +namespace SampleApplication.Controllers +{ + [Route("api/shapes")] + public class ShapesController : ControllerBase + { + + private readonly SampleDbContext _db; + + private readonly ShapeProcessor _calculator; + + public ShapesController( + + SampleDbContext db, + + ShapeProcessor calculator) + + { + + _db = db; + + _calculator = calculator; + + } + + [HttpPost] + + public IActionResult AddShapes(List shapes) + + { + + // TODO: + + // - Validate + + // - Ignore duplicate IDs + + // - Save via EF Core + + this._calculator.AddShapes(shapes); + + this._db.Shapes.AddRange(shapes); + + return Ok(shapes); + } + + [HttpGet("{id}")] + + public async Task Get(int id) + + { + + // TODO: + + var shape = await this._db.Shapes.Where(s => s.Id == id).FirstOrDefaultAsync(); + + return Ok(shape); + } + + [HttpGet("stats")] + + public IActionResult Stats() + + { + + // TODO: + + throw new NotImplementedException(); + + } + + } +} diff --git a/SampleApplication/Entities/ShapeInput.cs b/SampleApplication/Entities/ShapeInput.cs new file mode 100644 index 0000000..a16e93a --- /dev/null +++ b/SampleApplication/Entities/ShapeInput.cs @@ -0,0 +1,13 @@ +using Microsoft.OpenApi; + +namespace SampleApplication.Entities +{ + public class ShapeInput + { + public int Id { get; set; } + + public ShapeType Type { get; set; } + + public double[] Dimensions { get; set; } + } +} diff --git a/SampleApplication/Migrations/20260119092501_InitialCreate.Designer.cs b/SampleApplication/Migrations/20260119092501_InitialCreate.Designer.cs new file mode 100644 index 0000000..445c38b --- /dev/null +++ b/SampleApplication/Migrations/20260119092501_InitialCreate.Designer.cs @@ -0,0 +1,49 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SampleApplication; + +#nullable disable + +namespace SampleApplication.Migrations +{ + [DbContext(typeof(SampleDbContext))] + [Migration("20260119092501_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("SampleApplication.Entities.ShapeInput", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.PrimitiveCollection("Dimensions") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Shapes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SampleApplication/Migrations/20260119092501_InitialCreate.cs b/SampleApplication/Migrations/20260119092501_InitialCreate.cs new file mode 100644 index 0000000..6c6a61f --- /dev/null +++ b/SampleApplication/Migrations/20260119092501_InitialCreate.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SampleApplication.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Shapes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Type = table.Column(type: "int", nullable: false), + Dimensions = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Shapes", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Shapes"); + } + } +} diff --git a/SampleApplication/Migrations/SampleDbContextModelSnapshot.cs b/SampleApplication/Migrations/SampleDbContextModelSnapshot.cs new file mode 100644 index 0000000..96923b0 --- /dev/null +++ b/SampleApplication/Migrations/SampleDbContextModelSnapshot.cs @@ -0,0 +1,46 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SampleApplication; + +#nullable disable + +namespace SampleApplication.Migrations +{ + [DbContext(typeof(SampleDbContext))] + partial class SampleDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("SampleApplication.Entities.ShapeInput", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.PrimitiveCollection("Dimensions") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Shapes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SampleApplication/Program.cs b/SampleApplication/Program.cs new file mode 100644 index 0000000..30bc4f6 --- /dev/null +++ b/SampleApplication/Program.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using SampleApplication; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); + +builder.Services.AddSingleton(); + +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/SampleApplication/Properties/launchSettings.json b/SampleApplication/Properties/launchSettings.json new file mode 100644 index 0000000..eba8d85 --- /dev/null +++ b/SampleApplication/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5078", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7203;http://localhost:5078", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/SampleApplication/SampleApplication.csproj b/SampleApplication/SampleApplication.csproj new file mode 100644 index 0000000..38cf46d --- /dev/null +++ b/SampleApplication/SampleApplication.csproj @@ -0,0 +1,20 @@ + + + + net10.0 + enable + enable + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/SampleApplication/SampleApplication.http b/SampleApplication/SampleApplication.http new file mode 100644 index 0000000..619cd2f --- /dev/null +++ b/SampleApplication/SampleApplication.http @@ -0,0 +1,6 @@ +@SampleApplication_HostAddress = http://localhost:5078 + +GET {{SampleApplication_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/SampleApplication/SampleDbContext.cs b/SampleApplication/SampleDbContext.cs new file mode 100644 index 0000000..0c83aa0 --- /dev/null +++ b/SampleApplication/SampleDbContext.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; + +namespace SampleApplication +{ + public class SampleDbContext : DbContext + { + public SampleDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet Shapes { get; set; } + } +} diff --git a/SampleApplication/ShapeProcessor.cs b/SampleApplication/ShapeProcessor.cs new file mode 100644 index 0000000..632c6fe --- /dev/null +++ b/SampleApplication/ShapeProcessor.cs @@ -0,0 +1,102 @@ +using SampleApplication.Entities; +using System.Net.Http.Headers; +using System.Reflection.Metadata.Ecma335; + +namespace SampleApplication +{ + public class ShapeProcessor + { + public List shapes = new List(); + public void AddShapes(IEnumerable shapes) + { + // TODO: + // - Ignore duplicate Ids + // - Validate dimensions + // - Store shapes in memory + + foreach(var shape in shapes) + { + if (!ValidateDimensions(shape)) + { + throw new ArgumentException("Invalid shape dimensions"); + } + + this.shapes.Add(shape); + } + } + + public (double Area, double Perimeter) GetResult(int id) + { + // TODO: + // - Throw KeyNotFoundException if not found + // - Compute area & perimeter + + if(!shapes.Any(s => s.Id == id)) + { + throw new KeyNotFoundException("Shape not found"); + } + + var shape = shapes.First(s => s.Id == id); + + return this.CalculateAreaPerimeter(shape); + } + + public (int TotalShapes, double TotalArea, int MaxPerimeterId) GetStatistics() + { + // TODO: + + var totalShapes = shapes.Count; + + double totalArea = shapes.Sum(s => this.CalculateAreaPerimeter(s).area); + + var maxPerimeterShape = shapes.OrderByDescending(s => this.CalculateAreaPerimeter(s).perimeter).FirstOrDefault(); + + return (totalShapes, totalArea, maxPerimeterShape != null ? maxPerimeterShape.Id : 0); + } + + private bool ValidateDimensions(ShapeInput shape) + { + return shape.Type switch + { + ShapeType.Circle => shape.Dimensions.Length == 1 && shape.Dimensions[0] > 0, + ShapeType.Rectangle => shape.Dimensions.Length == 2 && shape.Dimensions.All(d => d > 0), + ShapeType.Triangle => shape.Dimensions.Length == 2, + _ => false, + }; + } + + private (double area, double perimeter) CalculateAreaPerimeter(ShapeInput shape) + { + var perimeter = shape.Type switch + { + ShapeType.Circle => + 2 * Math.PI * shape.Dimensions[0], + ShapeType.Rectangle => + 2 * (shape.Dimensions[0] + shape.Dimensions[1]), + ShapeType.Triangle => this.CalculateTrianglePerimeter(shape), + _ => throw new NotSupportedException("Shape type not supported"), + }; + + var area = shape.Type switch + { + ShapeType.Circle => + Math.PI * shape.Dimensions[0] * shape.Dimensions[0], + ShapeType.Rectangle => + shape.Dimensions[0] * shape.Dimensions[1], + ShapeType.Triangle => + 0.5 * shape.Dimensions[0] * shape.Dimensions[1], + _ => throw new NotSupportedException("Shape type not supported"), + }; + + return (area, perimeter); + } + + private double CalculateTrianglePerimeter(ShapeInput shape) + { + double @base = shape.Dimensions[0]; + double height = shape.Dimensions[1]; + double hypotenuse = Math.Sqrt(@base * @base + height * height); + return @base + height + hypotenuse; + } + } +} diff --git a/SampleApplication/ShapeType.cs b/SampleApplication/ShapeType.cs new file mode 100644 index 0000000..837c562 --- /dev/null +++ b/SampleApplication/ShapeType.cs @@ -0,0 +1,9 @@ +namespace SampleApplication +{ + public enum ShapeType + { + Rectangle, + Circle, + Triangle + } +} diff --git a/SampleApplication/appsettings.Development.json b/SampleApplication/appsettings.Development.json new file mode 100644 index 0000000..393efbb --- /dev/null +++ b/SampleApplication/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=KBR\\SQLSERVER2022;Database=SampleAppDb;User Id=sa;Password=pass@1234;TrustServerCertificate=True" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/SampleApplication/appsettings.json b/SampleApplication/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/SampleApplication/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}