Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 49 additions & 12 deletions ContosoUniversity/ContosoUniversity.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,59 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Azure.Storage.Blobs, Version=12.24.0.0, Culture=neutral, PublicKeyToken=92742159e12e44c8, processorArchitecture=MSIL">
<HintPath>packages\Azure.Storage.Blobs.12.24.0\lib\netstandard2.0\Azure.Storage.Blobs.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Azure.Core, Version=1.44.1.0, Culture=neutral, PublicKeyToken=92742159e12e44c8, processorArchitecture=MSIL">
<HintPath>packages\Azure.Core.1.44.1\lib\netstandard2.0\Azure.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.ClientModel, Version=1.1.0.0, Culture=neutral, PublicKeyToken=92742159e12e44c8, processorArchitecture=MSIL">
<HintPath>packages\System.ClientModel.1.1.0\lib\netstandard2.0\System.ClientModel.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Memory.Data, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Memory.Data.6.0.0\lib\netstandard2.0\System.Memory.Data.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Azure.Storage.Common, Version=12.23.0.0, Culture=neutral, PublicKeyToken=92742159e12e44c8, processorArchitecture=MSIL">
<HintPath>packages\Azure.Storage.Common.12.23.0\lib\netstandard2.0\Azure.Storage.Common.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=6.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Diagnostics.DiagnosticSource.6.0.1\lib\net461\System.Diagnostics.DiagnosticSource.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Hashing, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.IO.Hashing.6.0.0\lib\net461\System.IO.Hashing.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Text.Encodings.Web, Version=6.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Text.Encodings.Web.6.0.1\lib\net461\System.Text.Encodings.Web.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Text.Json, Version=6.0.0.11, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Text.Json.6.0.11\lib\net461\System.Text.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.ApplicationServices" />
Expand Down Expand Up @@ -135,10 +183,6 @@
<HintPath>packages\Microsoft.Data.SqlClient.2.1.4\lib\net46\Microsoft.Data.SqlClient.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces">
<HintPath>packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\netstandard2.0\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions">
<HintPath>packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
<Private>True</Private>
Expand Down Expand Up @@ -187,10 +231,6 @@
<HintPath>packages\Microsoft.Extensions.Primitives.3.1.32\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Diagnostics.DiagnosticSource">
<HintPath>packages\System.Diagnostics.DiagnosticSource.4.7.1\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Collections.Immutable">
<HintPath>packages\System.Collections.Immutable.1.7.1\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
<Private>True</Private>
Expand All @@ -199,10 +239,6 @@
<HintPath>packages\System.ComponentModel.Annotations.4.7.0\lib\net461\System.ComponentModel.Annotations.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Identity.Client">
<HintPath>packages\Microsoft.Identity.Client.4.21.1\lib\net461\Microsoft.Identity.Client.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform">
Expand Down Expand Up @@ -242,6 +278,7 @@
<Compile Include="Models\SchoolViewModels\InstructorIndexData.cs" />
<Compile Include="PaginatedList.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\BlobStorageService.cs" />
<Compile Include="Services\NotificationService.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions ContosoUniversity/Controllers/BaseController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public abstract class BaseController : Controller
{
protected SchoolContext db;
protected NotificationService notificationService = new NotificationService();
protected BlobStorageService blobStorageService = new BlobStorageService();

public BaseController()
{
Expand Down Expand Up @@ -41,6 +42,7 @@ protected override void Dispose(bool disposing)
{
db?.Dispose();
notificationService?.Dispose();
blobStorageService?.Dispose();
}
base.Dispose(disposing);
}
Expand Down
83 changes: 33 additions & 50 deletions ContosoUniversity/Controllers/CoursesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public ActionResult Create([Bind(Include = "CourseID,Title,Credits,DepartmentID,
// Validate file type
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".bmp" };
var fileExtension = Path.GetExtension(teachingMaterialImage.FileName).ToLower();

if (!allowedExtensions.Contains(fileExtension))
{
ModelState.AddModelError("teachingMaterialImage", "Please upload a valid image file (jpg, jpeg, png, gif, bmp).");
Expand All @@ -72,20 +72,16 @@ public ActionResult Create([Bind(Include = "CourseID,Title,Credits,DepartmentID,

try
{
// Create uploads directory if it doesn't exist
var uploadsPath = Server.MapPath("~/Uploads/TeachingMaterials/");
if (!Directory.Exists(uploadsPath))
{
Directory.CreateDirectory(uploadsPath);
}
// Generate unique blob name
var blobName = $"course_{course.CourseID}_{Guid.NewGuid()}{fileExtension}";

// Generate unique filename
var fileName = $"course_{course.CourseID}_{Guid.NewGuid()}{fileExtension}";
var filePath = Path.Combine(uploadsPath, fileName);
// Upload to Azure Blob Storage
var blobUri = blobStorageService.UploadBlobAsync(
teachingMaterialImage.InputStream,
blobName,
teachingMaterialImage.ContentType).Result;

// Save file
teachingMaterialImage.SaveAs(filePath);
course.TeachingMaterialImagePath = $"~/Uploads/TeachingMaterials/{fileName}";
course.TeachingMaterialImagePath = blobUri;
}
catch (Exception ex)
{
Expand All @@ -97,10 +93,10 @@ public ActionResult Create([Bind(Include = "CourseID,Title,Credits,DepartmentID,

db.Courses.Add(course);
db.SaveChanges();

// Send notification for course creation
SendEntityNotification("Course", course.CourseID.ToString(), course.Title, EntityOperation.CREATE);

return RedirectToAction("Index");
}

Expand Down Expand Up @@ -137,7 +133,7 @@ public ActionResult Edit([Bind(Include = "CourseID,Title,Credits,DepartmentID,Te
// Validate file type
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".bmp" };
var fileExtension = Path.GetExtension(teachingMaterialImage.FileName).ToLower();

if (!allowedExtensions.Contains(fileExtension))
{
ModelState.AddModelError("teachingMaterialImage", "Please upload a valid image file (jpg, jpeg, png, gif, bmp).");
Expand All @@ -155,30 +151,22 @@ public ActionResult Edit([Bind(Include = "CourseID,Title,Credits,DepartmentID,Te

try
{
// Create uploads directory if it doesn't exist
var uploadsPath = Server.MapPath("~/Uploads/TeachingMaterials/");
if (!Directory.Exists(uploadsPath))
// Delete old blob if exists
if (!string.IsNullOrEmpty(course.TeachingMaterialImagePath))
{
Directory.CreateDirectory(uploadsPath);
blobStorageService.DeleteBlobAsync(course.TeachingMaterialImagePath).Wait();
}

// Generate unique filename
var fileName = $"course_{course.CourseID}_{Guid.NewGuid()}{fileExtension}";
var filePath = Path.Combine(uploadsPath, fileName);
// Generate unique blob name
var blobName = $"course_{course.CourseID}_{Guid.NewGuid()}{fileExtension}";

// Delete old file if exists
if (!string.IsNullOrEmpty(course.TeachingMaterialImagePath))
{
var oldFilePath = Server.MapPath(course.TeachingMaterialImagePath);
if (System.IO.File.Exists(oldFilePath))
{
System.IO.File.Delete(oldFilePath);
}
}
// Upload new blob to Azure Blob Storage
var blobUri = blobStorageService.UploadBlobAsync(
teachingMaterialImage.InputStream,
blobName,
teachingMaterialImage.ContentType).Result;

// Save new file
teachingMaterialImage.SaveAs(filePath);
course.TeachingMaterialImagePath = $"~/Uploads/TeachingMaterials/{fileName}";
course.TeachingMaterialImagePath = blobUri;
}
catch (Exception ex)
{
Expand All @@ -190,10 +178,10 @@ public ActionResult Edit([Bind(Include = "CourseID,Title,Credits,DepartmentID,Te

db.Entry(course).State = EntityState.Modified;
db.SaveChanges();

// Send notification for course update
SendEntityNotification("Course", course.CourseID.ToString(), course.Title, EntityOperation.UPDATE);

return RedirectToAction("Index");
}
ViewBag.DepartmentID = new SelectList(db.Departments, "DepartmentID", "Name", course.DepartmentID);
Expand Down Expand Up @@ -223,22 +211,17 @@ public ActionResult DeleteConfirmed(int id)
Course course = db.Courses.Find(id);
var courseTitle = course.Title;

// Delete associated image file if it exists
// Delete associated image from Azure Blob Storage if it exists
if (!string.IsNullOrEmpty(course.TeachingMaterialImagePath))
{
var filePath = Server.MapPath(course.TeachingMaterialImagePath);
if (System.IO.File.Exists(filePath))
try
{
try
{
System.IO.File.Delete(filePath);
}
catch (Exception ex)
{
// Log the error but don't prevent deletion of the course
// In a production application, you would log this error properly
System.Diagnostics.Debug.WriteLine($"Error deleting file: {ex.Message}");
}
blobStorageService.DeleteBlobAsync(course.TeachingMaterialImagePath).Wait();
}
catch (Exception ex)
{
// Log the error but don't prevent deletion of the course
System.Diagnostics.Debug.WriteLine($"Error deleting blob: {ex.Message}");
}
}

Expand Down
Loading