如何在Razor视图表单中构建基于MyType的IMyConfig对象
Let's break this down into two main parts: dynamically switching form sections based on the selected MyType, and properly binding submitted data to the correct IMyConfig implementation. Here's a step-by-step solution:
First, restructure your Razor view to wrap each config's input fields in a hidden container. We'll use jQuery to show/hide these containers when the dropdown changes.
Updated Razor View
@model MyModel <form asp-action="ActivityCreateSubmit"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label asp-for="Id" class="control-label"></label> <input asp-for="Id" class="form-control" /> <span asp-validation-for="Id" class="text-danger"></span> </div> <div class="form-group" style="display: none;"> <label asp-for="Name" class="control-label"></label> <input asp-for="Name" class="form-control" value="@ViewBag.AppId" /> <span asp-validation-for="Name" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="MyType" class="control-label"></label> <select id="selection" asp-for="MyType" asp-items="Html.GetEnumSelectList<MyType>()"> <option value="">Select...</option> </select> <span asp-validation-for="MyType" class="text-danger"></span> </div> <!-- First Config Section --> <div id="FirstConfigForm" class="config-form" style="display:none;"> <h4>First Configuration</h4> <div class="form-group"> <label>Cat</label> <input name="MyConfig.cat" class="form-control" /> </div> <div class="form-group"> <label>Dog</label> <input name="MyConfig.dog" class="form-control" /> </div> <div class="form-group"> <label>Lion</label> <input name="MyConfig.lion" class="form-control" /> </div> </div> <!-- Second Config Section --> <div id="SecondConfigForm" class="config-form" style="display:none;"> <h4>Second Configuration</h4> <div class="form-group"> <label>Bear</label> <input name="MyConfig.bear" class="form-control" /> </div> <div class="form-group"> <label>Pig</label> <input name="MyConfig.pig" type="number" class="form-control" /> </div> <div class="form-group"> <label>Fish</label> <input name="MyConfig.fish" class="form-control" /> </div> <div class="form-group"> <label>Shark</label> <input name="MyConfig.shark" class="form-control" /> </div> <div class="form-group"> <label>Dolphin</label> <input name="MyConfig.dolphin" class="form-control" /> </div> </div> <!-- Third Config Section (mirror the pattern above) --> <div id="ThirdConfigForm" class="config-form" style="display:none;"> <h4>Third Configuration</h4> <!-- Add your ThirdConfig fields here with name="MyConfig.[PropertyName]" --> </div> <div class="form-group"> <input type="submit" value="Create" class="btn btn-default" /> </div> </form> @section Scripts { <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script> $(document).ready(function() { // Initialize form visibility if editing an existing model const initialType = $("#MyType").val(); if (initialType) showConfigForm(initialType); // Listen for dropdown changes $("#selection").change(function() { const selectedType = $(this).val(); showConfigForm(selectedType); }); function showConfigForm(type) { // Hide all config sections $(".config-form").hide(); // Show the selected section if (type) $("#" + type + "Form").show(); } }); </script> }
Key Note: Use name="MyConfig.PropertyName" for input fields—this tells ASP.NET Core how to map values to the MyConfig property during binding.
Since IMyConfig is an interface, ASP.NET Core's default model binder doesn't know which concrete class to instantiate. You have two solid options:
Option 1: Custom Model Binder (Recommended for Scalability)
Create a binder that selects the correct IMyConfig implementation based on the selected MyType:
public class MyConfigModelBinder : IModelBinder { public async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); // Get the selected MyType from the request var myTypeValue = bindingContext.HttpContext.Request.Form["MyType"]; if (!Enum.TryParse<MyType>(myTypeValue, out var myType)) { bindingContext.ModelState.AddModelError("MyType", "Invalid configuration type selected"); return; } // Initialize the correct config class IMyConfig config = myType switch { MyType.FirstConfig => new FirstConfig(), MyType.SecondConfig => new SecondConfig(), MyType.ThirdConfig => new ThirdConfig(), // Add your ThirdConfig here _ => null }; if (config == null) { bindingContext.ModelState.AddModelError("MyType", "Unsupported configuration type"); return; } // Bind the config properties using the default binder var metadataProvider = bindingContext.HttpContext.RequestServices.GetRequiredService<IModelMetadataProvider>(); var configBindingContext = new ModelBindingContext( bindingContext.ActionContext, bindingContext.ValueProvider, metadataProvider.GetMetadataForType(config.GetType()), null, "MyConfig" ); var defaultBinder = bindingContext.HttpContext.RequestServices.GetRequiredService<IModelBinder>(); await defaultBinder.BindModelAsync(configBindingContext); if (configBindingContext.Result.IsModelSet) { bindingContext.Result = ModelBindingResult.Success(configBindingContext.Result.Model as IMyConfig); } else { bindingContext.ModelState.AddModelError("MyConfig", "Failed to bind configuration values"); } } }
Register the binder in your startup code (Program.cs/Startup.cs):
services.AddControllersWithViews(options => { options.ModelBinderProviders.Insert(0, new BinderTypeModelBinderProvider(typeof(IMyConfig), new MyConfigModelBinder())); });
Or annotate the MyConfig property directly:
public class MyModel { public int Id { get; set; } public string Name { get; set; } public MyType MyType { get; set; } [ModelBinder(typeof(MyConfigModelBinder))] public IMyConfig MyConfig { get; set; } }
Option 2: Manual Binding in Controller (Simpler for Small Apps)
If you prefer a lighter approach, manually map form values to the correct config class in your controller:
public ActionResult ActivityCreateSubmit(MyModel model) { if (!ModelState.IsValid) return View("ActivityCreate", model); // Create the correct config instance based on MyType IMyConfig config = model.MyType switch { MyType.FirstConfig => new FirstConfig { cat = Request.Form["MyConfig.cat"], dog = Request.Form["MyConfig.dog"], lion = Request.Form["MyConfig.lion"] }, MyType.SecondConfig => new SecondConfig { bear = Request.Form["MyConfig.bear"], pig = int.TryParse(Request.Form["MyConfig.pig"], out var pig) ? pig : 0, fish = Request.Form["MyConfig.fish"], shark = Request.Form["MyConfig.shark"], dolphin = Request.Form["MyConfig.dolphin"] }, MyType.ThirdConfig => new ThirdConfig { // Map your ThirdConfig fields here }, _ => null }; if (config == null) { ModelState.AddModelError("MyType", "Invalid configuration type"); return View("ActivityCreate", model); } model.MyConfig = config; _activityTable.AddActivity(model); // Use your injected ActivityTable return RedirectToAction("Index", "Home"); }
To validate config fields, add data annotations to your concrete config classes and use asp-for instead of manual name attributes. For example:
public class FirstConfig : IMyConfig { [Required] public string cat { get; set; } [Required] public string dog { get; set; } // ... rest of the class }
Then update the input in the view:
<input asp-for="MyConfig.cat" class="form-control" /> <span asp-validation-for="MyConfig.cat" class="text-danger"></span>
内容的提问来源于stack exchange,提问作者asdasd2a43qaad

