Here are notes based on the key topics from Lecture 2.1 - Persisting Data for your revision:
-
Textbook Usage: The course emphasizes using view model classes over directly interacting with persistent storage (database) through controllers, unlike the textbook examples.
-
Project Template: You will use a pre-configured database in upcoming weeks to build interactive web apps. Later, you'll learn to create your own data store.
-
System Design and Architecture: The course follows a layered architecture:
- Views interact with controllers.
- Controllers use view model classes for data exchange.
- Manager class handles business logic and data service operations.
- Data store (local database) is accessed indirectly using Entity Framework (EF).
-
App Data: Relational databases are used, stored in the
App_Data
folder. The connection is managed via theWeb.config
file using a connection string. -
Local vs. Host-Based Databases:
- During development, a local instance of SQL Server (within Visual Studio) is used.
- When deploying, a host-based SQL Server can be configured.
-
Object-Relational Mapper (ORM):
- Entity Framework is used to map C# objects to database tables, simplifying database interactions.
- The Data Context serves as the gateway to the data store and supports common operations (querying, adding, updating, deleting).
-
Design Model Classes:
- These classes represent the entities in the database (e.g., tables), where each property maps to a column in the table.
-
Role:
- The Manager class centralizes app and business logic. Controllers never directly interact with the database; they call Manager methods.
- Methods in the Manager class can handle tasks like fetching data, adding records, etc.
-
Layered Architecture:
- Manager classes promote a safer and more structured coding style by isolating database operations from controllers and views.
- Data passed between Manager methods and controllers is handled by view model classes.
-
Purpose:
- View models are tailored for specific use cases and simplify interactions between the UI and the underlying data model.
- They are customized to fit exactly what is needed for display, form population, or form submission.
-
Advantages:
- Increased flexibility in how data is presented and processed.
- Enhances security by preventing direct interaction between user data and the data model.
-
Typical Use Cases:
- Add: To create new entities.
- Base: For retrieving and displaying data.
- Edit: To update existing records.
-
Role:
- AutoMapper helps map between design model classes (data from the database) and view model classes.
- It works based on conventions, mapping properties with matching names and types automatically.
-
Usage:
- In this course, you are required to use the instance-based API of AutoMapper, as using the static API will result in grading penalties.
- Definition: Domain models represent the core entities or objects in your application's business logic. These models directly map to the concepts or things in the problem domain (the real world or the business you are working with).
- Purpose: Domain models are used to interact with the database and manage business rules. They often mirror the structure of your database tables in applications using ORM (Object-Relational Mapping) tools like Entity Framework.
- Example:
- If you are building an e-commerce application, domain models could include
Product
,Customer
,Order
, etc. - For instance, a
Customer
domain model might look like:public class Customer { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public DateTime DateOfBirth { get; set; } public ICollection<Order> Orders { get; set; } }
- If you are building an e-commerce application, domain models could include
- Definition: View models are data transfer objects (DTOs) designed specifically for the presentation layer (the user interface). They contain only the data that is needed for the UI, making them lighter and more focused compared to domain models.
- Purpose: View models simplify the data that is sent to the front end (or received from the front end). They decouple the domain/business models from the user interface, ensuring that sensitive data (e.g., passwords) or unnecessary data (e.g., internal system information) is not exposed.
- Example:
- In the same e-commerce application, a view model could represent only the necessary data for displaying customer information on the UI. For instance, a
CustomerBaseViewModel
might only include basic information:public class CustomerBaseViewModel { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } }
- In the same e-commerce application, a view model could represent only the necessary data for displaying customer information on the UI. For instance, a
-
Purpose:
- Domain Models: Represent the structure of your business or database entities. They often include relationships between objects (e.g., a customer having multiple orders).
- View Models: Simplified objects used for displaying data in the user interface. They may omit fields that are unnecessary for the UI or sensitive to business logic.
-
Usage:
- Domain Models: Used internally in your application's business logic or to interact with a database.
- View Models: Used to transfer data between the controller and the view/UI. They are mapped from domain models but serve a presentation-specific purpose.
-
Structure:
- Domain Models: Can include all properties related to the business entity, including relationships (e.g., orders for a customer).
- View Models: Typically contain only the information required by the view (e.g., a customer’s name and email but not their orders).
Yes, you're exactly right! The purpose of AutoMapper is to simplify the process of converting (or mapping) between domain models and view models (or other data transfer objects, DTOs). AutoMapper automates this process, avoiding the need to manually copy properties between these objects. Let me explain how this works step-by-step:
AutoMapper is a library that helps you:
- Map domain models to view models (and vice versa).
- Automatically handle data transformation between objects that have similar or related structures.
- Reduce boilerplate code that you would otherwise write manually to transfer data from one model to another.
Here’s how AutoMapper fits into the flow:
-
Domain Model and Data Fetching:
- First, you fetch data from the database using a domain model. The domain model often has all the data, including relationships, detailed properties, and sensitive information that are part of the business logic.
- Example:
public class Customer { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public string PasswordHash { get; set; } // Sensitive data public ICollection<Order> Orders { get; set; } // Relationships }
-
Mapping Domain Model to View Model:
-
After fetching the domain model, you map it to a view model using AutoMapper.
-
The view model contains only the data needed for the UI, so you strip out unnecessary or sensitive fields (e.g.,
PasswordHash
). -
AutoMapper automates the mapping of fields with the same or similar names between the domain model and the view model.
-
Example of View Model:
public class CustomerBaseViewModel { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } // Only what UI needs }
-
AutoMapper setup:
var config = new MapperConfiguration(cfg => { cfg.CreateMap<Customer, CustomerBaseViewModel>(); }); var mapper = config.CreateMapper();
-
Using AutoMapper to map the domain model to the view model:
var customerViewModels = mapper.Map<IEnumerable<Customer>, IEnumerable<CustomerBaseViewModel>>(customers);
-
-
Displaying the View Model in the UI:
- The view model is sent to the UI, and it only contains the data that the view needs (e.g., customer name, email). This ensures that sensitive or irrelevant data is not exposed to the front end.
-
Receiving Input (Reverse Mapping):
-
When the user submits a form (e.g., adding or editing a customer), the input data is often sent as a view model (such as
CustomerAddViewModel
). -
You then use AutoMapper to map the view model back to the domain model.
-
Example:
var newCustomer = mapper.Map<CustomerAddViewModel, Customer>(customerAddViewModel);
-
You can then use the
newCustomer
object to save it in the database.
-
-
Reduces Boilerplate Code: Without AutoMapper, you'd have to manually map each property from the domain model to the view model and vice versa. AutoMapper handles this for you, making the code cleaner and easier to maintain.
- Example of manual mapping without AutoMapper:
var customerViewModel = new CustomerBaseViewModel { Id = customer.Id, Name = customer.Name, Email = customer.Email };
- Example of manual mapping without AutoMapper:
-
Consistent and Maintainable: AutoMapper ensures that mappings are handled consistently across your application. If your model changes (e.g., you add or remove properties), you only need to update the mappings in one place.
-
Separation of Concerns: The domain model remains focused on the business logic and data storage, while the view model is optimized for displaying data in the UI. AutoMapper bridges these two models seamlessly.
- Domain Models represent all the data fetched from the database, including sensitive and detailed information.
- View Models contain only the necessary information required for the UI, making the data lighter and more secure.
- AutoMapper automatically maps domain models to view models and vice versa, saving you from writing repetitive code and ensuring that only the required data is sent to the front end.
In short, AutoMapper helps you extract only the needed data from your domain model and pass it to the view in a clean and efficient way.
This Customer/Index.cshtml
code is the Razor view for displaying a list of customers in a table format. Here’s a breakdown of the key components:
@model IEnumerable<MyFirstWebApp.Models.CustomerBaseViewModel>
- This line declares the type of the model the view will work with. In this case, the view expects a collection of
CustomerBaseViewModel
objects (which is likely passed from the controller).
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
- Displays an "Index" header.
- The
@Html.ActionLink("Create New", "Create")
generates a hyperlink that points to theCreate
action method in the controller. It allows users to navigate to a page for creating a new customer.
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.FirstName)
</th>
<!-- More columns here -->
</tr>
- A table is used to display customer information.
@Html.DisplayNameFor(...)
renders the display name for each property (likeFirstName
,LastName
, etc.), based on what is defined in theCustomerBaseViewModel
class.
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<!-- More columns here -->
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.CustomerId }) |
@Html.ActionLink("Details", "Details", new { id=item.CustomerId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.CustomerId })
</td>
</tr>
}
@foreach (var item in Model)
iterates over the collection of customers (Model
) passed to the view.- Inside the loop, each customer (
item
) is displayed row by row. @Html.DisplayFor(...)
renders the value of each property for the current customer (item
), likeFirstName
,LastName
, etc.
@Html.ActionLink("Edit", "Edit", new { id=item.CustomerId }) |
@Html.ActionLink("Details", "Details", new { id=item.CustomerId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.CustomerId })
- These are links that let the user interact with each customer:
- Edit: Takes the user to the
Edit
action method for the specific customer, identified byCustomerId
. - Details: Takes the user to the
Details
action method to view details for that customer. - Delete: Takes the user to the
Delete
action method to delete the customer.
- Edit: Takes the user to the
Each of these action links passes the CustomerId
as a route parameter to the corresponding action method in the controller.
- This Razor view:
- Displays a table with customer information.
- Uses
Html.DisplayNameFor
to display the column names. - Uses
Html.DisplayFor
to show the values for each customer in the list. - Provides "Edit", "Details", and "Delete" actions for each customer.
Let me know if you'd like any further clarification!
In an ASP.NET project, using Scaffolding is a way to automatically generate code for common CRUD (Create, Read, Update, Delete) operations, views, and controllers based on the data models you have defined in your project. It helps developers save time by creating the necessary files and structures instead of writing everything manually.
Specifically, when you scaffold a view in an ASP.NET project, the Scaffolding tool will:
- Analyze the model: It looks at your data model (e.g., a class that represents a table in a database).
- Generate HTML and Razor code: It automatically creates a
.cshtml
file, which includes the necessary Razor syntax and HTML to display or manage the model’s data in the view. - Generate CRUD views: If you're using Entity Framework, scaffolding can generate views like:
- Create.cshtml: For creating new records.
- Edit.cshtml: For updating records.
- Delete.cshtml: For deleting records.
- Details.cshtml: For viewing individual record details.
- Index.cshtml: For listing records.
- Right-click on the Controllers folder or the Views folder.
- Select Add > New Scaffolded Item.
- Choose the type of scaffold, such as MVC View Page, Controller with Views using Entity Framework, etc.
- Select the model and data context to generate the view based on that model.
Scaffolding speeds up the development process and ensures consistent code structure across your project.
Here’s a basic explanation and usage of DbContext
and DbSet
in an ASP.NET Core or Entity Framework Core project:
- DbContext is a class in Entity Framework that represents a session with the database. It allows you to interact with your database by querying, saving, and performing other database-related operations.
- It manages the entity objects during runtime and handles their state changes, allowing you to commit them to the database.
- DbSet represents a collection of entities of a specific type (usually corresponding to a database table). It provides methods for querying and working with the entities (such as adding, removing, updating, etc.).
Let's say you have a simple Product
entity, and you want to perform basic CRUD operations using DbContext
and DbSet
.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
You’ll create a class that inherits from DbContext
. Inside it, you define properties of type DbSet<T>
for each entity type you want to work with (in this case, Product
).
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
// DbSet for the Product entity
public DbSet<Product> Products { get; set; }
// Additional DbSets for other entities can be added here.
}
In ASP.NET Core, you need to register ApplicationDbContext
in the Startup.cs
file or Program.cs
file.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// Other services...
}
// Other methods...
}
Now, in a controller or service class, you can use ApplicationDbContext
to interact with the database.
public class ProductService
{
private readonly ApplicationDbContext _context;
public ProductService(ApplicationDbContext context)
{
_context = context;
}
// Add a new Product
public async Task AddProduct(Product product)
{
_context.Products.Add(product); // Adds the new product to the DbSet
await _context.SaveChangesAsync(); // Commits the changes to the database
}
// Get all Products
public async Task<List<Product>> GetProducts()
{
return await _context.Products.ToListAsync(); // Returns the list of products
}
// Get a Product by ID
public async Task<Product> GetProductById(int id)
{
return await _context.Products.FindAsync(id); // Finds the product by its primary key (Id)
}
// Update a Product
public async Task UpdateProduct(Product product)
{
_context.Products.Update(product); // Marks the product as modified
await _context.SaveChangesAsync(); // Commits the changes
}
// Delete a Product
public async Task DeleteProduct(int id)
{
var product = await _context.Products.FindAsync(id); // Find the product by Id
if (product != null)
{
_context.Products.Remove(product); // Removes the product
await _context.SaveChangesAsync(); // Commits the deletion
}
}
}
- DbContext: Represents a session with the database, holds configurations and manages entity states.
- DbSet: Represents a collection (table) of a specific entity type (e.g.,
Product
) and provides methods to query, add, update, and delete records from the database.
This basic setup shows how to work with DbContext
and DbSet
to perform database operations in an ASP.NET project.
In the provided code:
ds.Entry(obj).CurrentValues.SetValues(customer);
Here’s a breakdown of how it works:
-
ds.Entry(obj)
:- The
ds
is likely your Entity Framework DbContext (data context). - The
Entry(obj)
method retrieves the EntityEntry for the given objectobj
. This entry provides access to metadata and state information about the entityobj
in the context. It allows you to work with that entity, which exists in the context’s in-memory representation.
- The
-
CurrentValues
:- After retrieving the entity
obj
, you access itsCurrentValues
. CurrentValues
is a property of the EntityEntry and represents the current values of the entity's properties as a collection of DbPropertyValues. These values reflect the current state of the entity within the context.
- After retrieving the entity
-
SetValues(customer)
:- This method sets or updates the current values of the entity (
obj
) with the values from the providedcustomer
object. - It maps matching properties by name between
obj
andcustomer
. If a property incustomer
matches a property inobj
, the value fromcustomer
will overwrite the value inobj
. - It ignores navigation properties (i.e., relationships) and only updates scalar properties (like strings, integers, etc.).
- Any properties in
customer
that don’t exist inobj
are ignored.
- This method sets or updates the current values of the entity (
- This pattern is helpful when you want to update an entity’s properties without manually assigning each property one by one.
- It’s particularly useful in scenarios like editing an existing record in a database using Entity Framework, where you load an entity (e.g.,
obj
), then apply changes from a model or another object (e.g.,customer
), and finally save those changes back to the database.
SetValues(customer)
provides an efficient way to update an entity in the database by copying matching property values from another object (in this case, customer
), while ignoring properties that don’t exist or navigation properties that represent relationships.
Data annotation in ASP.NET is a way to apply validation rules, display formatting, and model behavior by using attributes directly in the model classes. These attributes help ensure that the data meets certain requirements before being processed or saved to a database. They are part of the System.ComponentModel.DataAnnotations
namespace.
- [Required]: Ensures the property must have a value.
- [StringLength]: Specifies the maximum and/or minimum length of a string.
- [Range]: Restricts a value to a specific numeric range.
- [RegularExpression]: Validates the property against a specific regular expression pattern.
- [EmailAddress]: Validates that the property contains a valid email address.
- [DisplayName]: Sets a custom display name for a property in UI.
- [Compare]: Compares two properties to ensure they have the same value.
- Simplifies Validation: You can define validation rules directly in the model, ensuring that validation is centralized and consistent.
- Improves Code Readability: Attributes provide an easy-to-read, declarative way to enforce business rules and data integrity.
- Reduces Boilerplate Code: Eliminates the need for repetitive validation logic in the controller or service layer.
- Integrates with Client-Side Validation: When used with frameworks like ASP.NET MVC, it can automatically apply validation rules to the client side.
- Easy Error Handling: Validation attributes automatically generate user-friendly error messages when rules are violated.
LINQ (Language Integrated Query) is a powerful feature in .NET that allows developers to query data from various sources (such as collections, databases, XML, or even web services) in a consistent and readable way, using syntax integrated directly into C# or VB.NET. It simplifies data manipulation by offering a declarative approach to working with data structures like arrays, lists, and other collections.
- Query Syntax: Resembles SQL-like syntax for querying data.
- Method Syntax: Uses methods like
Where
,Select
,OrderBy
, etc., available as extension methods. - Deferred Execution: LINQ queries are not executed until the results are iterated, offering optimization.
-
Querying Collections (LINQ to Objects):
- LINQ allows querying and manipulating in-memory collections (e.g., arrays, lists).
- Example:
var numbers = new List<int> { 1, 2, 3, 4, 5 }; var evenNumbers = from num in numbers where num % 2 == 0 select num;
-
Database Operations (LINQ to SQL / Entity Framework):
- LINQ can be used to query relational databases in a strongly-typed manner, providing compile-time checking and IntelliSense support.
- Example:
var employees = from emp in context.Employees where emp.Department == "HR" select emp;
-
XML Querying (LINQ to XML):
- LINQ enables querying and manipulating XML documents in an intuitive manner.
- Example:
var xml = XDocument.Load("file.xml"); var elements = from elem in xml.Descendants("Item") where (int)elem.Element("Price") > 100 select elem;
-
Data Transformation:
- LINQ is effective in transforming data from one format to another using
Select
,GroupBy
,OrderBy
, etc. - Example:
var employees = new List<Employee>(); var names = employees.Select(e => new { e.FirstName, e.LastName });
- LINQ is effective in transforming data from one format to another using
-
Parallel LINQ (PLINQ):
- With PLINQ, LINQ queries can be parallelized to improve performance on multi-core systems.
- Example:
var largeCollection = Enumerable.Range(1, 1000000); var evenNumbers = largeCollection.AsParallel().Where(n => n % 2 == 0);
LINQ enhances readability, reduces code complexity, and provides a unified way to work with different types of data sources in .NET applications.
Fluent Query Expression Syntax refers to a style of writing queries in LINQ using method chaining, which allows for more fluent and readable code. This syntax is often called Method Syntax in LINQ, and it contrasts with Query Syntax, which resembles SQL-like expressions.
In Fluent Syntax, you invoke methods provided by LINQ (like Where
, Select
, OrderBy
) as extension methods on collections (e.g., IEnumerable<T>
, IQueryable<T>
). Each method call returns an updated collection or queryable object, allowing for chaining multiple methods in a readable sequence.
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
// Fluent Syntax to filter even numbers and sort them in descending order
var result = numbers.Where(n => n % 2 == 0)
.OrderByDescending(n => n);
- Method Chaining: Each LINQ method is called on the result of the previous one, producing a fluent chain.
- Extension Methods: LINQ methods are implemented as extension methods, allowing them to be called directly on collections.
- Deferred Execution: The query is not executed until the result is iterated (e.g., with a
foreach
loop).
Where
: Filters elements based on a condition.Select
: Projects each element into a new form.OrderBy
,OrderByDescending
: Orders the elements in ascending or descending order.GroupBy
: Groups elements by a specified key.Join
: Joins two collections based on keys.Take
,Skip
: Limits or skips elements in the collection.
-
Fluent (Method) Syntax:
var evenNumbers = numbers.Where(n => n % 2 == 0) .OrderByDescending(n => n);
-
Query Syntax:
var evenNumbers = from n in numbers where n % 2 == 0 orderby n descending select n;
In a .NET Core MVC web app using Entity Framework (EF), associated data typically refers to the relationships between entities in your database, such as one-to-many, many-to-many, or one-to-one relationships. Here's how it works:
-
Defining Relationships in Models: In your models, you define relationships between entities using navigation properties and data annotations or Fluent API to specify foreign keys and constraints.
-
One-to-Many:
public class Category { public int CategoryId { get; set; } public string Name { get; set; } public List<Product> Products { get; set; } } public class Product { public int ProductId { get; set; } public string Name { get; set; } public int CategoryId { get; set; } // Foreign Key public Category Category { get; set; } // Navigation Property }
-
Many-to-Many (with a join entity):
public class Student { public int StudentId { get; set; } public string Name { get; set; } public List<StudentCourse> StudentCourses { get; set; } } public class Course { public int CourseId { get; set; } public string Title { get; set; } public List<StudentCourse> StudentCourses { get; set; } } public class StudentCourse { public int StudentId { get; set; } public Student Student { get; set; } public int CourseId { get; set; } public Course Course { get; set; } }
-
-
Configuring Relationships: You can configure relationships using Fluent API in the
OnModelCreating
method of yourDbContext
:protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Product>() .HasOne(p => p.Category) .WithMany(c => c.Products) .HasForeignKey(p => p.CategoryId); modelBuilder.Entity<StudentCourse>() .HasKey(sc => new { sc.StudentId, sc.CourseId }); }
-
CRUD Operations: When working with associated data, EF Core automatically manages the relationships. For example, when adding a new product to a category, the foreign key will be set automatically:
var category = _context.Categories.First(); var product = new Product { Name = "New Product", Category = category }; _context.Products.Add(product); _context.SaveChanges();
-
Eager Loading and Lazy Loading: When retrieving associated data, you can use eager loading to load related entities in the same query using
Include
:var productsWithCategories = _context.Products .Include(p => p.Category) .ToList();
Or use lazy loading, where related entities are loaded only when accessed, if lazy loading proxies are configured.
-
View Models for Associated Data: In your MVC views, you can use view models to combine related data to present it in the UI:
public class ProductViewModel { public string ProductName { get; set; } public string CategoryName { get; set; } }
This is a basic overview of how associated data works in a .NET Core MVC app with EF Core.
While Query Syntax is often easier to read for complex queries (especially for SQL-like operations), Fluent Syntax is more flexible, supports method chaining, and is the only way to express some operations (like Skip
, Take
, or certain joins). Both syntaxes are interchangeable, and they often produce the same underlying query.
- When fetching a Program object that has associated data (like Subject), it doesn't include related objects by default.
- To include associated data, you need to explicitly use eager loading by calling the
Include()
method.
- First, include the associated Subject data using
Include()
:var progs = ds.Programs.Include("Subjects");
- Then, add filtering to only include Program objects where
Credential
is "Degree":var progs = ds.Programs.Include("Subjects").Where(p => p.Credential == "Degree");
- You can chain
Include()
,Where()
, andOrderBy()
to filter and sort the data:var progs = ds.Programs .Include("Subjects") .Where(p => p.Credential == "Degree") .OrderBy(p => p.Code);
- This retrieves Program objects with the "Degree" credential, includes their associated Subject objects, and sorts them by Code.
- Get all: Avoid using
Include()
in "get all" queries because it can result in fetching too much data. If necessary, limit the data usingTake()
. - Get one: It’s common to use
Include()
in "get one" queries because the included data often adds value.
- Lazy Loading: Fetches associated data only when accessed later. No
Include()
is needed. - Eager Loading: Fetches the object and its associated data all at once using
Include()
.
- Lazy Loading is not recommended for web apps because web apps use the HTTP protocol, where each request-response cycle is stateless.
- Always use Eager Loading (
Include()
) in web apps to ensure all necessary data is fetched at once.
Lazy loading is ineffective in web apps and can cause issues, so it's best to disable it.
- Scaffolding a view: Generates views for common actions like list, create, edit, etc.
- HTML Helpers: Help render HTML elements easily in Razor views.
- Associated entities: Displaying related entities in views.
- Item-selection elements are necessary when dealing with associated entities (e.g., dropdowns, checkboxes).
- We will also explore Bootstrap for styling and responsive design in ASP.NET MVC.
- Scaffolding works well for display-only views, rendering objects in tables or description lists.
- When dealing with associated entities, scaffolding does not render related objects; you need to use item-selection controls like dropdowns or checkboxes.
- Scaffolding can generate input fields for adding, editing, and deleting data but does not handle item-selection elements like dropdowns or checkboxes.
HTML Form Element | Scaffolded? |
---|---|
Label | Yes |
Text box | Yes |
Multiline text area | Yes |
Button | Yes |
Hidden field | Yes |
Dropdown list | No |
Listbox | No |
Radio button group | No |
Checkbox group | No |
- Dropdown list:
<select> <option>Option 1</option> <option>Option 2</option> </select>
- Listbox (Single/Multiple Selection): Allows multiple selections with the
multiple
attribute:<select size="4" multiple> <option>Option 1</option> <option>Option 2</option> </select>
- Radio buttons and Checkbox groups: Allow for single or multiple selections:
<input type="radio" name="option" /> <input type="checkbox" name="option" />
- Since scaffolding doesn’t generate item-selection elements, you’ll need to hand-code these elements into your views.
- Strategy:
- Use scaffolding to generate base views.
- Edit the view code to add item-selection elements manually.
- Best practice: Create a view model that includes properties for the data required in item-selection elements.
- View Models: Separate models for “add” and “edit” scenarios are recommended.
- Example:
VenueEditFormViewModel
andVenueEditViewModel
for form input and submission data.
- Example:
- For single-selection: The
name
attribute in HTML must match a property in the view model (usually an int or string). - For multiple-selection: The property in the view model should be a collection (e.g.,
List<int>
orIEnumerable<int>
).
- Bootstrap is a popular framework for responsive and mobile-friendly web apps. It comes pre-installed with ASP.NET MVC.
- Bootstrap 3.x is included by default, but the latest version is 5.x (don’t update it for course assignments due to compatibility).
- Bootstrap is primarily CSS-based. You use it by adding class names to HTML elements:
- Grid system for layout.
- Forms for styling input elements.
- Tables for displaying data.
- Buttons for actions.
- Bootstrap uses a 12-column grid system to create layouts that automatically adjust for different screen sizes.
.col-xs-*
: Extra small devices..col-sm-*
: Small devices..col-md-*
: Medium devices..col-lg-*
: Large devices.
- Wrap form controls in
.form-group
and use.form-control
to get consistent, well-spaced, and styled forms. - Always pair
<label>
elements with form controls to ensure accessibility and proper alignment.
- SelectList is a class that helps package data for use in item-selection elements (e.g., dropdown lists).
- Best practice: Add "List" as a suffix for SelectList properties in the view model (e.g.,
ManufacturerList
).
public SelectList ManufacturerList { get; set; }
- IEnumerable items: The collection of items (e.g., Products, Employees).
- dataValueField: The property used for the "value" attribute (e.g.,
Id
). - dataTextField: The property used for the visible text (e.g.,
Name
).
- HTML Helpers make writing HTML simpler and integrate well with model binding.
- Common HTML Helpers for item-selection elements:
@Html.DropDownList()
@Html.ListBox()
@Html.DropDownList("ManufacturerId", Model.ManufacturerList, new { @class = "form-control" })
- Use these helpers for generating dropdowns and listboxes, but you’ll need to hand-code radio button and checkbox groups.
- Bootstrap helps make your web apps responsive and visually consistent.
- Item-selection elements (dropdowns, checkboxes) require hand-coding and integrating data through view models.
- SelectList is crucial for creating dynamic item-selection elements.
- HTML Helpers simplify form creation and help maintain a strong connection between the view and model data.
By following these practices, you can efficiently manage item-selection elements and improve the user experience with Bootstrap.
- The POST method is used to handle form data submitted by the user.
- It works similarly to other "add new" use cases.
- The difference here is that the PRG (Post-Redirect-Get) pattern is used, where after adding a new item, the user is redirected to the Details view in the
VehiclesController
.
-
In traditional routing, URLs might look like this:
https://host.example.com/manufacturers/addvehicle/56
- This URL structure suggests that you’re adding a vehicle with an ID of 56, but the context might not be clear.
-
With Attribute Routing, you can make the URL more intuitive, such as:
https://host.example.com/manufacturers/56/addvehicle
- This URL clearly indicates adding a vehicle to manufacturer 56.
- Attribute Routing allows you to directly define routes in your controller actions.
- For example, in
ManufacturersController
, you can specify the following route for adding a vehicle:[Route("manufacturers/{id}/addvehicle")] public ActionResult AddVehicle(int? id)
- To use Attribute Routing, you need to enable it in your ASP.NET MVC app.
- This is done by adding the following line to the
RegisterRoutes
method inRouteConfig
:routes.MapMvcAttributeRoutes();
- Attribute Routing can work alongside the conventional routing methods and provides more flexibility in defining custom resource URLs.
By using attribute routing, you can create more readable and meaningful URLs that clearly reflect the resource being accessed or the operation being performed, making your web app's navigation more intuitive.