diff --git a/ContosoUniversity/ContosoUniversity.csproj b/ContosoUniversity/ContosoUniversity.csproj
index 8f49c50d..66ae2904 100644
--- a/ContosoUniversity/ContosoUniversity.csproj
+++ b/ContosoUniversity/ContosoUniversity.csproj
@@ -45,11 +45,59 @@
4
+
+ packages\Azure.Storage.Blobs.12.24.0\lib\netstandard2.0\Azure.Storage.Blobs.dll
+ True
+
+
+ packages\Azure.Core.1.44.1\lib\netstandard2.0\Azure.Core.dll
+ True
+
+
+ packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll
+ True
+
+
+ packages\System.ClientModel.1.1.0\lib\netstandard2.0\System.ClientModel.dll
+ True
+
+
+ packages\System.Memory.Data.6.0.0\lib\netstandard2.0\System.Memory.Data.dll
+ True
+
+
+ packages\Azure.Storage.Common.12.23.0\lib\netstandard2.0\Azure.Storage.Common.dll
+ True
+
+
+ packages\System.Diagnostics.DiagnosticSource.6.0.1\lib\net461\System.Diagnostics.DiagnosticSource.dll
+ True
+
+
+ packages\System.IO.Hashing.6.0.0\lib\net461\System.IO.Hashing.dll
+ True
+
+
+ packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll
+ True
+
+
+ packages\System.Text.Encodings.Web.6.0.1\lib\net461\System.Text.Encodings.Web.dll
+ True
+
+
+ packages\System.Text.Json.6.0.11\lib\net461\System.Text.Json.dll
+ True
+
+
+ packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll
+ True
+
@@ -135,10 +183,6 @@
packages\Microsoft.Data.SqlClient.2.1.4\lib\net46\Microsoft.Data.SqlClient.dll
True
-
- packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\netstandard2.0\Microsoft.Bcl.AsyncInterfaces.dll
- True
-
packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll
True
@@ -187,10 +231,6 @@
packages\Microsoft.Extensions.Primitives.3.1.32\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll
True
-
- packages\System.Diagnostics.DiagnosticSource.4.7.1\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
packages\System.Collections.Immutable.1.7.1\lib\netstandard2.0\System.Collections.Immutable.dll
True
@@ -199,10 +239,6 @@
packages\System.ComponentModel.Annotations.4.7.0\lib\net461\System.ComponentModel.Annotations.dll
True
-
- packages\Microsoft.Identity.Client.4.21.1\lib\net461\Microsoft.Identity.Client.dll
- True
-
@@ -242,6 +278,7 @@
+
diff --git a/ContosoUniversity/Controllers/BaseController.cs b/ContosoUniversity/Controllers/BaseController.cs
index 5e46cefb..ca4f4707 100644
--- a/ContosoUniversity/Controllers/BaseController.cs
+++ b/ContosoUniversity/Controllers/BaseController.cs
@@ -10,6 +10,7 @@ public abstract class BaseController : Controller
{
protected SchoolContext db;
protected NotificationService notificationService = new NotificationService();
+ protected BlobStorageService blobStorageService = new BlobStorageService();
public BaseController()
{
@@ -41,6 +42,7 @@ protected override void Dispose(bool disposing)
{
db?.Dispose();
notificationService?.Dispose();
+ blobStorageService?.Dispose();
}
base.Dispose(disposing);
}
diff --git a/ContosoUniversity/Controllers/CoursesController.cs b/ContosoUniversity/Controllers/CoursesController.cs
index 32706841..43e87e77 100644
--- a/ContosoUniversity/Controllers/CoursesController.cs
+++ b/ContosoUniversity/Controllers/CoursesController.cs
@@ -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).");
@@ -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)
{
@@ -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");
}
@@ -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).");
@@ -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)
{
@@ -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);
@@ -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}");
}
}
diff --git a/ContosoUniversity/Services/BlobStorageService.cs b/ContosoUniversity/Services/BlobStorageService.cs
new file mode 100644
index 00000000..b88867f0
--- /dev/null
+++ b/ContosoUniversity/Services/BlobStorageService.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Configuration;
+using System.IO;
+using System.Threading.Tasks;
+using Azure.Storage.Blobs;
+using Azure.Storage.Blobs.Models;
+
+namespace ContosoUniversity.Services
+{
+ public class BlobStorageService : IDisposable
+ {
+ private readonly BlobServiceClient _blobServiceClient;
+ private readonly string _containerName;
+
+ public BlobStorageService()
+ {
+ // Get configuration from Web.config
+ string connectionString = ConfigurationManager.AppSettings["AzureStorageBlob:ConnectionString"];
+ _containerName = ConfigurationManager.AppSettings["AzureStorageBlob:ContainerName"];
+
+ if (string.IsNullOrEmpty(connectionString))
+ {
+ throw new InvalidOperationException("AzureStorageBlob:ConnectionString configuration is missing in Web.config");
+ }
+
+ if (string.IsNullOrEmpty(_containerName))
+ {
+ throw new InvalidOperationException("AzureStorageBlob:ContainerName configuration is missing in Web.config");
+ }
+
+ // Create BlobServiceClient using connection string
+ _blobServiceClient = new BlobServiceClient(connectionString);
+
+ // Ensure container exists
+ EnsureContainerExists();
+ }
+
+ private void EnsureContainerExists()
+ {
+ try
+ {
+ var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
+
+ // Create container if it doesn't exist with public read access for blobs
+ containerClient.CreateIfNotExists(PublicAccessType.Blob);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error ensuring container exists: {ex.Message}");
+ // Don't throw - container might already exist or will be created on first upload
+ }
+ }
+
+ ///
+ /// Uploads a file to Azure Blob Storage
+ ///
+ /// File stream to upload
+ /// Name for the blob (e.g., "course_1045_guid.jpg")
+ /// Content type of the file (e.g., "image/jpeg")
+ /// URI of the uploaded blob
+ public async Task UploadBlobAsync(Stream stream, string blobName, string contentType)
+ {
+ try
+ {
+ var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
+ var blobClient = containerClient.GetBlobClient(blobName);
+
+ // Set blob upload options with content type
+ var uploadOptions = new BlobUploadOptions
+ {
+ HttpHeaders = new BlobHttpHeaders
+ {
+ ContentType = contentType
+ }
+ };
+
+ // Upload the blob
+ await blobClient.UploadAsync(stream, uploadOptions);
+
+ // Return the blob URI
+ return blobClient.Uri.ToString();
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error uploading blob {blobName}: {ex.Message}");
+ throw new ApplicationException($"Failed to upload file to blob storage: {ex.Message}", ex);
+ }
+ }
+
+ ///
+ /// Deletes a blob from Azure Blob Storage
+ ///
+ /// Full URI of the blob to delete
+ /// True if deleted, false if not found
+ public async Task DeleteBlobAsync(string blobUri)
+ {
+ if (string.IsNullOrEmpty(blobUri))
+ {
+ return false;
+ }
+
+ try
+ {
+ // Extract blob name from URI
+ var uri = new Uri(blobUri);
+ var blobName = uri.Segments[uri.Segments.Length - 1];
+
+ var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
+ var blobClient = containerClient.GetBlobClient(blobName);
+
+ // Delete the blob if it exists
+ var response = await blobClient.DeleteIfExistsAsync();
+ return response.Value;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error deleting blob {blobUri}: {ex.Message}");
+ // Return false instead of throwing to avoid breaking course deletion
+ return false;
+ }
+ }
+
+ ///
+ /// Gets the URI for a blob
+ ///
+ /// Name of the blob
+ /// URI of the blob
+ public string GetBlobUri(string blobName)
+ {
+ var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
+ var blobClient = containerClient.GetBlobClient(blobName);
+ return blobClient.Uri.ToString();
+ }
+
+ ///
+ /// Checks if a blob exists
+ ///
+ /// Full URI of the blob
+ /// True if exists, false otherwise
+ public async Task BlobExistsAsync(string blobUri)
+ {
+ if (string.IsNullOrEmpty(blobUri))
+ {
+ return false;
+ }
+
+ try
+ {
+ var uri = new Uri(blobUri);
+ var blobName = uri.Segments[uri.Segments.Length - 1];
+
+ var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
+ var blobClient = containerClient.GetBlobClient(blobName);
+
+ var response = await blobClient.ExistsAsync();
+ return response.Value;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ public void Dispose()
+ {
+ // BlobServiceClient doesn't require explicit disposal
+ }
+ }
+}
diff --git a/ContosoUniversity/Web.config b/ContosoUniversity/Web.config
index f9257e0e..b1522a23 100644
--- a/ContosoUniversity/Web.config
+++ b/ContosoUniversity/Web.config
@@ -17,6 +17,9 @@
+
+
+