BindNever and BindRequired Attribute in ASP.NET Core MVC

BindNever and BindRequired Attribute in ASP.NET Core MVC

In this article, I will discuss BindNever and BindRequired Attribute in ASP.NET Core MVC Applications with Examples. Please read our previous article discussing Displaying and Formatting Data Annotation Attributes in ASP.NET Core MVC Applications.

In ASP.NET Core MVC, the BindNever and BindRequired attributes are used to control model binding behavior for specific properties on model classes. These attributes ensure that our application handles input data correctly, which is important for maintaining the integrity and security of the data.

BindNever Attribute in ASP.NET Core MVC

The BindNever attribute is used to explicitly prevent a property from being bound by model binding. This is useful in scenarios where you do not want certain data to be updated from the request, such as properties that are set programmatically and should not be changed by user input. For instance, an Id or CreatedBy field should not be altered once the object is created. Here’s how you might use the BindNever attribute:

using Microsoft.AspNetCore.Mvc.ModelBinding;
public class Product
{
    public int Id { get; set; }

    [BindNever]
    public string CreatedBy { get; set; } // This property will not be bound to user input

    public string Name { get; set; }
    public decimal Price { get; set; }
}

In this example, the CreatedBy property will not receive any data from an incoming request, regardless of whether it appears in the request. This helps protect critical fields that should not be altered directly by external users.

When to use BindNever:
  • Security: To ensure that critical properties, like IDs or user roles, are not changed by the end user.
  • Integrity: To maintain the integrity of data that should not be altered after its initial creation.
  • Control: To explicitly control which properties should be excluded from the binding process.
BindRequired Attribute in ASP.NET Core MVC

The BindRequired attribute, on the other hand, specifies that a property must be present in the input data of a request; otherwise, model binding will fail. This attribute is useful for ensuring that necessary fields are not omitted when data is submitted. Example of using the BindRequired attribute:

public class RegistrationViewModel
{
    [BindRequired]
    public string Username { get; set; }

    [BindRequired]
    public string Email { get; set; }

    public string Password { get; set; }
}

In this example, both Username and Email must be present in the request data; otherwise, the model binding will fail, making this method effective for enforcing mandatory fields.

When to use BindRequired:
  • Validation: Ensure the user provides essential fields during form submissions or API calls.
  • Data Integrity: To prevent the application from processing incomplete or corrupt data.
  • User Feedback: To generate automatic validation responses that inform the user of required data that was not submitted.

Example of BindNever and BindRequired Attribute in ASP.NET Core MVC:

Suppose you build an e-commerce application using ASP.NET Core MVC with an Order model. The Order model includes sensitive information that should not be updated directly from form data, such as OrderTotal and PaymentProcessed. Other fields like CustomerName, OrderDate, and ShippingAddress should also be updated. So, create a class file named Order.cs and copy and paste the following code. Inside this file, we are creating all our models.

using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace DataAnnotationsDemo.Models
{
    public class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }

    public class OrderItem
    {
        public int ProductId { get; set; } // Foreign key to Product
        public int Quantity { get; set; }
        public decimal Price { get; set; } // This can be the selling price which might include discounts etc.
        [BindNever]
        public Product? Product { get; set; } // Navigation property to Product
    }

    public class Order
    {
        public int OrderId { get; set; }

        [BindRequired]
        public string CustomerName { get; set; }

        [BindRequired]
        public DateTime OrderDate { get; set; }

        [BindNever]
        public decimal OrderTotal { get; set; }

        [BindRequired]
        public string ShippingAddress { get; set; }

        public string PhoneNumber { get; set; }

        [BindNever]
        public bool PaymentProcessed { get; set; }
        public List<OrderItem> Items { get; set; }
    }
}

In the Order model:

  • ShippingAddress is a required field as it’s crucial for processing the delivery.
  • PhoneNumber is optional but can be useful for contacting the customer regarding their order.
  • PaymentProcessed is marked with BindNever to ensure it’s not modified directly by form submission, reflecting whether payment has been securely processed on the server side.
  • Items represent a list of OrderItem, detailing the items included in the order.
Add a Controller for the Order

Create a new Empty MVC Controller in the Controllers folder called OrderController.cs, and then copy and paste the following code.

using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;

namespace DataAnnotationsDemo.Controllers
{
    public class OrderController : Controller
    {
        private List<Product> listProducts;
        public OrderController()
        {
            listProducts = new List<Product>()
            {
                new Product { ProductId = 1, Name = "Laptop", Price = 999.99m },
                new Product { ProductId = 3, Name = "Mobile", Price = 300.50m },
                new Product { ProductId = 4, Name = "Desktop", Price = 1330.50m }
            };
        }

        [HttpGet]
        public IActionResult Create()
        {
            var order = new Order
            {
                OrderDate = DateTime.Today,
                Items = listProducts.Select(p => new OrderItem
                {
                    ProductId = p.ProductId,
                    Quantity = 0, // Initialize with zero, let user choose the amount
                    Price = p.Price,
                    Product = p // Link the product details directly
                }).ToList()
            };
            return View(order);
        }

        [HttpPost]
        public IActionResult Create(Order order)
        {
            if (ModelState.IsValid)
            {
                // Reapply correct prices and link products to guard against tampering
                foreach (var item in order.Items)
                {
                    var product = listProducts.FirstOrDefault(p => p.ProductId == item.ProductId);
                    if (product != null)
                    {
                        item.Price = product.Price;
                        item.Product = product;
                    }
                }

                order.OrderTotal = CalculateOrderTotal(order);
                order.PaymentProcessed = ProcessPayment(order);

                return RedirectToAction("Success");
            }

            // Ensure products are linked if returning to view after a failed validation
            foreach (var item in order.Items)
            {
                item.Product = listProducts.FirstOrDefault(p => p.ProductId == item.ProductId);
            }

            return View(order);
        }

        private decimal CalculateOrderTotal(Order order)
        {
            decimal total = 0m;
            foreach (var item in order.Items)
            {
                total += item.Price * item.Quantity;
            }
            return total;
        }

        private bool ProcessPayment(Order order)
        {
            // Assume payment processing always succeeds for this example
            return true;
        }

        public IActionResult Success()
        {
            return View();
        }
    }
}
Create the View

Create a new View named Create.cshtml in the Views/Order folder, and then copy and paste the following code.

@model DataAnnotationsDemo.Models.Order
@{
    ViewData["Title"] = "Place Order";
}
<h2>Create Order</h2>
<form asp-controller="Order" asp-action="Create" method="Post">
    <div asp-validation-summary="All" class="text-danger mb-3"></div>
    <div class="form-group row mb-3">
        <label asp-for="CustomerName" class="col-sm-2 col-form-label">Customer Name:</label>
        <div class="col-sm-10">
            <input asp-for="CustomerName" class="form-control">
            <span asp-validation-for="CustomerName" class="text-danger"></span>
        </div>
    </div>

    <div class="form-group row mb-3">
        <label asp-for="OrderDate" class="col-sm-2 col-form-label">Order Date:</label>
        <div class="col-sm-10">
            <input asp-for="OrderDate" type="date" class="form-control">
            <span asp-validation-for="OrderDate" class="text-danger"></span>
        </div>
    </div>

    <div class="form-group row mb-3">
        <label asp-for="ShippingAddress" class="col-sm-2 col-form-label">Shipping Address:</label>
        <div class="col-sm-10">
            <input asp-for="ShippingAddress" class="form-control">
            <span asp-validation-for="ShippingAddress" class="text-danger"></span>
        </div>
    </div>

    <div class="form-group row mb-3">
        <label asp-for="PhoneNumber" class="col-sm-2 col-form-label">Phone Number:</label>
        <div class="col-sm-10">
            <input asp-for="PhoneNumber" class="form-control">
        </div>
    </div>

    <h3>Order Items</h3>
    <table class="table table-bordered">
        <colgroup>
            <col style="width: 25%;">
            <col style="width: 25%;">
            <col style="width: 25%;">
            <col style="width: 25%;">
        </colgroup>
        <thead>
            <tr>
                <th>Item Name</th>
                <th>Quantity</th>
                <th>Unit Price</th>
                <th>Total Price</th>
            </tr>
        </thead>
        <tbody>
            @for (int i = 0; i < Model.Items.Count; i++)
            {
                <tr>
                    <td>
                        <span>@Model.Items[i].Product?.Name</span>
                        <input type="hidden" asp-for="@Model.Items[i].ProductId" />
                    </td>
                    <td>
                        <input asp-for="@Model.Items[i].Quantity" type="number" class="form-control quantity" />
                    </td>
                    <td class="text-right">
                        <span>@Model.Items[i].Price.ToString("C")</span>
                        <input type="hidden" asp-for="@Model.Items[i].Price" class="unit-price" />
                    </td>
                    <td class="text-right">
                        <span class="total-price">@((Model.Items[i].Price * Model.Items[i].Quantity).ToString("C"))</span>
                    </td>
                </tr>
            }
        </tbody>
    </table>

    <div class="form-group row">
        <div class="col-sm-12 d-flex justify-content-end">
            <div class="d-inline-flex align-items-baseline">
                <strong class="mr-2">Total Amount:</strong>
                <span id="totalAmount" style="padding-left: 15px;">@Model.Items.Sum(i => i.Quantity * i.Price).ToString("C")</span>
            </div>
        </div>
    </div>

    <button type="submit" class="btn btn-primary">Submit Order</button>
</form>

@section Scripts {
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        $(document).ready(function () {
            $('.quantity').change(function () {
                var total = 0;
                $('tbody tr').each(function () {
                    var $row = $(this);
                    var qty = parseInt($row.find('.quantity').val(), 10);
                    var price = parseFloat($row.find('.unit-price').val()); // Get the unit price from the hidden input

                    var totalPrice = qty * price;

                    // Update the displayed total price for each item
                    $row.find('.total-price').text('$' + totalPrice.toFixed(2));

                    // Add to overall total
                    total += totalPrice;
                });

                // Update the total amount displayed
                $('#totalAmount').text('$' + total.toFixed(2));
            });
        });
    </script>
}

Create another View named Success.cshtml in the Views/Order folder and then copy and paste the following code.

@{
    ViewData["Title"] = "Order Success";
}

<h2>Success</h2>
<p>Your order has been successfully processed!</p>
Testing the Application:

Now, run the application and navigate to the Order/Create URL, which will display the following page. Please fill out the form, select the quantity of the Item, and then click on the Submit Order button, as shown in the image below.

BindNever and BindRequired Attribute in ASP.NET Core MVC Applications with Examples

Once you click the Submit Order button, the order should be processed, and you should get the following success message.

Differences Between Required and BindRequired in ASP.NET Core MVC

Points to Remember:
  • Security: Using BindNever can be part of a strategy to prevent over-posting attacks, in which users might attempt to set properties they should not be able to set.
  • Validation: BindRequired does not replace validation attributes like [Required]. While BindRequired ensures the presence of a property in the incoming data, [Required] validates that a property is not null after binding has occurred.
  • [BindNever] is used to protect specific properties from being affected by model binding, usually for security reasons
  • [BindRequired] ensures that certain fields are included in the request, which is crucial for data completeness and validation.

Differences Between Required and BindRequired in ASP.NET Core MVC

In ASP.NET Core MVC, the Required and BindRequired attributes are often confused because they imply some form of “required-ness” for model properties. However, they serve different purposes and are used in different contexts. Understanding the Differences between them is important for correctly implementing data validation and model-binding behavior in your applications.

Required Attribute
  • Context: Data Annotation for Validation
  • Purpose: The Required attribute is part of the System.ComponentModel.DataAnnotations namespace and is used primarily for model validation. It ensures that a property has a value; it is mainly used to enforce that a field should not be null or empty during model validation.
  • Behavior: When a model is validated (usually after model binding has occurred), if a property marked with the Required attribute does not have a value, the model state is considered invalid. This attribute affects the server-side validation process and is also used to generate client-side validation rules.
Example of Required Attribute:
using System.ComponentModel.DataAnnotations;

public class RegisterModel
{
    [Required]
    public string Email { get; set; }  // Must have a non-null/non-empty value
}
BindRequired Attribute
  • Context: Model Binding
  • Purpose: The BindRequired attribute, from the Microsoft.AspNetCore.Mvc.ModelBinding namespace handles model binding behavior. It is used to indicate that a property must be present in the incoming data for binding to succeed. If the property is not present in the incoming request, the model state becomes invalid.
  • Behavior: Unlike Required, BindRequired does not concern itself with validating the property’s content (whether empty or null) but rather with its presence in the request. This is useful for ensuring that key parts of the model are not omitted in the request.
Example of BindRequired attribute usage:
using Microsoft.AspNetCore.Mvc.ModelBinding;

public class UpdateModel
{
    [BindRequired]
    public int Id { get; set; }  // Must be present in the incoming request
}
Key Differences Between Required and BindRequired in ASP.NET Core
  • Validation vs. Binding: Required validates that a property has a value after binding has occurred, whereas BindRequired ensures that the property is included in the incoming data before or during binding.
  • Effect on ModelState: Both can make ModelState invalid for different reasons: Required if the value is null or empty, and BindRequired if the key is missing in the request data.
  • Use Case: Use Required Attribute when you need to ensure that a property is not empty or null (common for forms and data entry scenarios). Use BindRequired when you need to ensure that certain data elements are actually present in the incoming request, typically important in APIs and data update scenarios.

In the next article, I will discuss Multiple Real-Time Examples of Data Annotations in ASP.NET Core MVC. In this article, I explain BindNever and BindRequired Attributes in the ASP.NET Core MVC application with examples. I hope you enjoy this article on BindNever and BindRequired Attribute in ASP.NET Core MVC.

Leave a Reply

Your email address will not be published. Required fields are marked *