Skip to content

Commit 8c0481c

Browse files
committed
Demo
1 parent 3b95995 commit 8c0481c

File tree

11 files changed

+298
-58
lines changed

11 files changed

+298
-58
lines changed

src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using Microsoft.Extensions.Options;
1212
using Microsoft.Extensions.Validation;
1313

14+
#pragma warning disable ASP0029
15+
1416
[assembly: MetadataUpdateHandler(typeof(Microsoft.AspNetCore.Components.Forms.EditContextDataAnnotationsExtensions))]
1517

1618
namespace Microsoft.AspNetCore.Components.Forms;
@@ -70,9 +72,7 @@ private sealed partial class DataAnnotationsEventSubscriptions : IDisposable
7072
private readonly IServiceProvider? _serviceProvider;
7173
private readonly ValidationMessageStore _messages;
7274
private readonly ValidationOptions? _validationOptions;
73-
#pragma warning disable ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
7475
private readonly IValidatableInfo? _validatorTypeInfo;
75-
#pragma warning restore ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
7676
private readonly Dictionary<string, FieldIdentifier> _validationPathToFieldIdentifierMapping = new();
7777

7878
[UnconditionalSuppressMessage("Trimming", "IL2066", Justification = "Model types are expected to be defined in assemblies that do not get trimmed.")]
@@ -82,11 +82,11 @@ public DataAnnotationsEventSubscriptions(EditContext editContext, IServiceProvid
8282
_serviceProvider = serviceProvider;
8383
_messages = new ValidationMessageStore(_editContext);
8484
_validationOptions = _serviceProvider?.GetService<IOptions<ValidationOptions>>()?.Value;
85-
#pragma warning disable ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
85+
8686
_validatorTypeInfo = _validationOptions != null && _validationOptions.TryGetValidatableTypeInfo(_editContext.Model.GetType(), out var typeInfo)
8787
? typeInfo
8888
: null;
89-
#pragma warning restore ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
89+
9090
_editContext.OnFieldChanged += OnFieldChanged;
9191
_editContext.OnValidationRequested += OnValidationRequested;
9292

@@ -164,7 +164,6 @@ private void ValidateWithDefaultValidator(ValidationContext validationContext)
164164
}
165165
}
166166

167-
#pragma warning disable ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
168167
private bool TryValidateTypeInfo(ValidationContext validationContext)
169168
{
170169
if (_validatorTypeInfo is null)
@@ -212,7 +211,6 @@ private bool TryValidateTypeInfo(ValidationContext validationContext)
212211
}
213212

214213
return true;
215-
216214
}
217215
private void AddMapping(ValidationErrorContext context)
218216
{

src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
55
<IsShippingPackage>false</IsShippingPackage>
66
<Nullable>enable</Nullable>
7+
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
8+
</PropertyGroup>
9+
10+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
11+
<NoWarn>$(NoWarn);SYSLIB0057;ASP0029</NoWarn>
12+
</PropertyGroup>
13+
14+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
15+
<NoWarn>$(NoWarn);SYSLIB0057;ASP0029</NoWarn>
716
</PropertyGroup>
817

918
<ItemGroup>
@@ -21,4 +30,8 @@
2130
<Reference Include="Microsoft.AspNetCore.Mvc" />
2231
</ItemGroup>
2332

33+
<ItemGroup>
34+
<ProjectReference Include="$(RepoRoot)/src/Validation/gen/Microsoft.Extensions.Validation.ValidationsGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
35+
</ItemGroup>
36+
2437
</Project>
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
@using System.ComponentModel.DataAnnotations
2+
@using BlazorUnitedApp.ValidationModels
3+
@using Microsoft.AspNetCore.Components.Forms
4+
@rendermode InteractiveServer
5+
6+
<EditForm id="complex-validation-form" Model="@order" OnValidSubmit="() => isValid = true" OnInvalidSubmit="() => isValid = false">
7+
8+
<DataAnnotationsValidator />
9+
10+
<div class="container mt-4">
11+
<h4>Order Details</h4>
12+
<div class="mb-3">
13+
<label for="orderName" class="form-label">Order Name</label>
14+
<InputText id="orderName" @bind-Value="order.OrderName" class="form-control" />
15+
<ValidationMessage For="@(() => order.OrderName)" />
16+
</div>
17+
18+
<hr />
19+
20+
<h4>Customer Details</h4>
21+
<div class="card mb-3">
22+
<div class="card-body">
23+
<h5>Contact (at least one)</h5>
24+
<div class="mb-3">
25+
<label for="customerEmail" class="form-label">Email</label>
26+
<InputText id="customerEmail" @bind-Value="order.CustomerDetails.Email" class="form-control" />
27+
<ValidationMessage For="@(() => order.CustomerDetails.Email)" />
28+
<ValidationMessage For="@(() => order.CustomerDetails)" />
29+
</div>
30+
<div class="mb-3">
31+
<label for="customerPhoneNumber" class="form-label">Phone Number</label>
32+
<InputText id="customerPhoneNumber" @bind-Value="order.CustomerDetails.PhoneNumber" class="form-control" />
33+
<ValidationMessage For="@(() => order.CustomerDetails.PhoneNumber)" />
34+
<ValidationMessage For="@(() => order.CustomerDetails)" />
35+
</div>
36+
<h5>Payment Address</h5>
37+
<div class="card mb-3">
38+
<div class="card-body">
39+
<div class="row">
40+
<div class="mb-3 col-sm-8">
41+
<label for="paymentStreet" class="form-label">Street</label>
42+
<InputText id="paymentStreet" @bind-Value="order.CustomerDetails.PaymentAddress.Street" class="form-control" />
43+
<ValidationMessage For="@(() => order.CustomerDetails.PaymentAddress.Street)" />
44+
</div>
45+
<div class="mb-3 col-sm">
46+
<label for="paymentZipCode" class="form-label">Zip Code</label>
47+
<InputText id="paymentZipCode" @bind-Value="order.CustomerDetails.PaymentAddress.ZipCode" class="form-control" />
48+
<ValidationMessage For="@(() => order.CustomerDetails.PaymentAddress.ZipCode)" />
49+
</div>
50+
</div>
51+
</div>
52+
</div>
53+
54+
<h5>Shipping Address</h5>
55+
<div class="card mb-3">
56+
<div class="card-body">
57+
<div class="row">
58+
<div class="mb-3 col-sm-8">
59+
<label for="shippingStreet" class="form-label">Street</label>
60+
<InputText id="shippingStreet" @bind-Value="order.CustomerDetails.ShippingAddress.Street" class="form-control" />
61+
<ValidationMessage For="@(() => order.CustomerDetails.ShippingAddress.Street)" />
62+
</div>
63+
<div class="mb-3 col-sm">
64+
<label for="shippingZipCode" class="form-label">Zip Code</label>
65+
<InputText id="shippingZipCode" @bind-Value="order.CustomerDetails.ShippingAddress.ZipCode" class="form-control" />
66+
<ValidationMessage For="@(() => order.CustomerDetails.ShippingAddress.ZipCode)" />
67+
</div>
68+
</div>
69+
</div>
70+
</div>
71+
</div>
72+
</div>
73+
74+
<hr />
75+
76+
<h4>Order Items</h4>
77+
@if (order.OrderItems.Any())
78+
{
79+
for (int i = 0; i < order.OrderItems.Count; i++)
80+
{
81+
var itemIndex = i;
82+
<div class="card mb-3">
83+
<div class="card-header d-flex justify-content-between align-items-center">
84+
<span>Item @(itemIndex + 1)</span>
85+
<button type="button" class="btn btn-sm btn-danger" @onclick="() => RemoveOrderItem(itemIndex)">Remove</button>
86+
</div>
87+
<div class="card-body">
88+
<div class="row">
89+
<div class="mb-3 col-sm-8">
90+
<label for="@($"productName_{itemIndex}")" class="form-label">Product Name</label>
91+
<InputText id="@($"productName_{itemIndex}")" @bind-Value="order.OrderItems[itemIndex].ProductName" class="form-control" />
92+
<ValidationMessage For="@(() => order.OrderItems[itemIndex].ProductName)" />
93+
</div>
94+
<div class="mb-3 col-sm">
95+
<label for="@($"quantity_{itemIndex}")" class="form-label">Quantity</label>
96+
<InputNumber id="@($"quantity_{itemIndex}")" @bind-Value="order.OrderItems[itemIndex].Quantity" class="form-control" />
97+
<ValidationMessage For="@(() => order.OrderItems[itemIndex].Quantity)" />
98+
</div>
99+
</div>
100+
</div>
101+
</div>
102+
}
103+
}
104+
else
105+
{
106+
<p>No order items. Add one below.</p>
107+
}
108+
109+
<button type="button" class="btn btn-success mb-3" @onclick="AddOrderItem">Add Order Item</button>
110+
111+
<hr />
112+
113+
<div class="mb-3">
114+
<button type="submit" id="submit-form" class="btn btn-primary">Submit Order</button>
115+
</div>
116+
117+
@if (isValid.HasValue && isValid.Value)
118+
{
119+
<div class="alert alert-success" role="alert">
120+
Order submitted successfully!
121+
</div>
122+
}
123+
else if (isValid.HasValue && isValid.Value == false)
124+
{
125+
<div class="alert alert-danger" role="alert">
126+
There were validation errors. Please check the form.
127+
</div>
128+
}
129+
<ValidationSummary />
130+
</div>
131+
</EditForm>
132+
133+
@code {
134+
private OrderModel order = new OrderModel();
135+
private bool? isValid = null;
136+
137+
private void HandleValidSubmit()
138+
{
139+
isValid = true;
140+
}
141+
142+
private void HandleInvalidSubmit()
143+
{
144+
isValid = false;
145+
}
146+
147+
private void AddOrderItem()
148+
{
149+
order.OrderItems.Add(new OrderItemModel());
150+
}
151+
152+
private void RemoveOrderItem(int index)
153+
{
154+
if (index >= 0 && index < order.OrderItems.Count)
155+
{
156+
order.OrderItems.RemoveAt(index);
157+
}
158+
}
159+
}
Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,7 @@
11
@page "/"
2-
@using BlazorUnitedApp.Data;
3-
<PageTitle>Index</PageTitle>
4-
5-
<EditForm Model="Value" method="POST" OnSubmit="DisplayCustomer" FormName="customer">
6-
<div>
7-
<label>
8-
<span>Name</span>
9-
<InputText @bind-Value="Value!.Name" />
10-
</label>
11-
</div>
12-
<AddressEditor @bind-Value="Value.BillingAddress" />
13-
<input type="submit" value="Send" />
14-
</EditForm>
15-
16-
@if (_submitted)
17-
{
18-
<!-- Display customer data -->
19-
<h3>Customer</h3>
20-
<p>Name: @Value!.Name</p>
21-
<p>Street: @Value.BillingAddress.Street</p>
22-
<p>City: @Value.BillingAddress.City</p>
23-
<p>State: @Value.BillingAddress.State</p>
24-
<p>Zip: @Value.BillingAddress.Zip</p>
25-
}
26-
27-
@code {
2+
@using BlazorUnitedApp.Components;
3+
@rendermode InteractiveServer
284

29-
public void DisplayCustomer()
30-
{
31-
_submitted = true;
32-
}
33-
34-
[SupplyParameterFromForm] Customer? Value { get; set; }
35-
36-
protected override void OnInitialized() => Value ??= new();
5+
<PageTitle>Index</PageTitle>
376

38-
bool _submitted = false;
39-
public void Submit() => _submitted = true;
40-
}
7+
<ComplexValidationComponent />

src/Components/Samples/BlazorUnitedApp/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
builder.Services.AddSingleton<WeatherForecastService>();
1515

16+
builder.Services.AddValidation();
17+
1618
var app = builder.Build();
1719

1820
// Configure the HTTP request pipeline.

src/Components/Samples/BlazorUnitedApp/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"dotnetRunMessages": true,
1414
"launchBrowser": true,
1515
"applicationUrl": "http://localhost:5265",
16-
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
16+
//"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
1717
"environmentVariables": {
1818
"ASPNETCORE_ENVIRONMENT": "Development"
1919
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.ComponentModel.DataAnnotations;
5+
6+
namespace BlazorUnitedApp.ValidationModels;
7+
8+
public class AddressModel
9+
{
10+
[Required(ErrorMessage = "Street is required.")]
11+
public string? Street { get; set; }
12+
13+
[Required(ErrorMessage = "Zip Code is required.")]
14+
[StringLength(10, MinimumLength = 5, ErrorMessage = "Zip Code must be between 5 and 10 characters.")]
15+
public string? ZipCode { get; set; }
16+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.ComponentModel.DataAnnotations;
5+
using Microsoft.Extensions.Validation;
6+
7+
namespace BlazorUnitedApp.ValidationModels;
8+
9+
[ContactRequired]
10+
public class CustomerModel
11+
{
12+
[EmailAddress(ErrorMessage = "Invalid Email Address.")]
13+
public string? Email { get; set; }
14+
15+
[Phone(ErrorMessage = "Invalid Phone Number.")]
16+
public string? PhoneNumber { get; set; }
17+
18+
public AddressModel PaymentAddress { get; set; } = new AddressModel();
19+
20+
[SkipValidation]
21+
public AddressModel ShippingAddress { get; set; } = new AddressModel();
22+
}
23+
24+
public class ContactRequiredAttribute : ValidationAttribute
25+
{
26+
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
27+
{
28+
if (value is CustomerModel customer && string.IsNullOrWhiteSpace(customer.Email) && string.IsNullOrWhiteSpace(customer.PhoneNumber))
29+
{
30+
return new ValidationResult("At least one contact method (Email or Phone Number) is required.");
31+
}
32+
return ValidationResult.Success;
33+
}
34+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.ComponentModel.DataAnnotations;
5+
6+
namespace BlazorUnitedApp.ValidationModels;
7+
8+
public class OrderItemModel
9+
{
10+
[Required(ErrorMessage = "Product Name is required.")]
11+
public string? ProductName { get; set; }
12+
13+
[Range(1, 100, ErrorMessage = "Quantity must be between 1 and 100.")]
14+
public int Quantity { get; set; } = 1;
15+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.ComponentModel.DataAnnotations;
5+
using Microsoft.Extensions.Validation;
6+
7+
namespace BlazorUnitedApp.ValidationModels;
8+
9+
[ValidatableType]
10+
public class OrderModel : IValidatableObject
11+
{
12+
[Required(ErrorMessage = "Order Name is required.")]
13+
[StringLength(100, ErrorMessage = "Order Name cannot be longer than 100 characters.")]
14+
public string? OrderName { get; set; }
15+
16+
public CustomerModel CustomerDetails { get; set; } = new CustomerModel();
17+
18+
public List<OrderItemModel> OrderItems { get; set; } = [];
19+
20+
public OrderModel()
21+
{
22+
OrderItems.Add(new OrderItemModel());
23+
}
24+
25+
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
26+
{
27+
if (OrderItems.Count == 0)
28+
{
29+
yield return new ValidationResult("At least one order item is required.", [nameof(OrderItems)]);
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)