如何在ASP .NET MVC中仅验证部分模型?

时间:2021-05-31 11:25:50

I have a large model (large I mean model class contains a lot of fields/properties and each has at least one validation attribute (such as Required, MaxLength, MinLength etc)). Instead of creating one view with a lot of inputs for user to fill model with data I want to create several views where user will fill part of model fields and go to the next step (some kind of "wizard"). While redirecting between steps I store not fullfilled model object in Session. Something like below:

我有一个大型模型(大型我的意思是模型类包含很多字段/属性,每个都至少有一个验证属性(如Required,MaxLength,MinLength等))。我没有创建一个带有大量输入的视图供用户使用数据填充模型,而是想要创建多个视图,用户将填充部分模型字段并转到下一步(某种“向导”)。在步骤之间重定向时,我在Session中存储未填充的模型对象。如下所示:

Model:

模型:

public class ModelClass
{
    [MaxLength(100)] ...
    public string Prop1{get;set;}
    [MaxLength(100)] ...
    public string Prop2{get;set;}
    ...
    [Required][MaxLength(100)] ...
    public string Prop20{get;set;}
}

Controller:

控制器:

[HttpPost]
public ActionResult Step1(ModelClass postedModel)
{    
    // user posts only for example Prop1 and Prop2
    // so while submit I have completly emty model object
    // but with filled Prop1 and Prop2
    // I pass those two values to Session["model"]
    var originalModel = Session["model"] as ModelClass ?? new ModelClass();
    originalModel.Prop1 = postedModel.Prop1;
    originalModel.Prop2 = postedModel.Prop2;
    Session["model"] = originalModel;

    // and return next step view
    return View("Step2");
}

[HttpPost]
public ActionResult Step2(ModelClass postedModel)
{
    // Analogically the same
    // I have posted only Prop3 and Prop4

    var originalModel = Session["model"] as ModelClass;
    if (originalModel!=null)
    {
        originalModel.Prop3 = postedModel.Prop3;
        originalModel.Prop4 = postedModel.Prop4;
        Session["model"] = originalModel;

        // return next step view
        return View("Step3");
    }
    return View("SomeErrorViewIfSessionBrokesSomeHow")
}

Step1 view has inputs only for Prop1 and Prop2, Step2 view contains inputs for Prop3 and Prop4 etc.

Step1视图仅具有Prop1和Prop2的输入,Step2视图包含Prop3和Prop4等的输入。

BUT HERE IS THE THING

但这就是事

When user is on, for example, step 1, and fills Prop1 with value more than 100 characters length client side validation works fine. But, of course , I have to validate this value and on the server side in controller. If I had full model I'd just do the following:

当用户打开时,例如,步骤1,并使用超过100个字符长度的值填充Prop1,客户端验证工作正常。但是,当然,我必须在控制器中验证此值并在服务器端验证。如果我有完整模型,我只需执行以下操作:

if(!ModelState.IsValid) return View("the same view with the same model object");

so user has to fill the form again and correct. BUT on step 1 user has filled only 2 properties of 20, and I need to validate them. I can't use ModelState.IsValid because model state will be invalid. As You can see Prop20 is marked with [Required] attribute, when user submits Prop1 and Prop2, Prop20 is null and that's why ModelState is invalid. Of course I could allow user to go to step2, fill all of the steps and validate model state only on the last step but I don't want to allow user to go to step 2 if he filled step 1 incorrect. And I want this validation in controller. So the question is: How can I validate only part of the model? How can I verify that only some of the model properties match their validation attributes?

所以用户必须再次填写表格并更正。但是在步骤1中,用户只填写了20个属性,我需要验证它们。我不能使用ModelState.IsValid,因为模型状态将无效。正如您所看到的,Prop20标有[Required]属性,当用户提交Prop1和Prop2时,Prop20为null,这就是ModelState无效的原因。当然我可以允许用户转到步骤2,填写所有步骤并仅在最后一步验证模型状态,但是如果他填写第1步不正确,我不想让用户转到第2步。我希望在控制器中进行此验证。所以问题是:我如何只验证模型的一部分?如何验证只有部分模型属性与其验证属性匹配?

4 个解决方案

#1


14  

One possible solution:

一种可能的方案:

  1. Use ModelState.IsValidField(string key);

    使用ModelState.IsValidField(string key);

    if (ModelState.IsValidField("Name") && ModelState.IsValidField("Address"))
    { ... }
    

Then at the end when everything is done use:

然后在一切结束时使用:

if(ModelState.IsValid) { .. }

#2


9  

I think the most elegant way is to do it like that:

我认为最优雅的方式是这样做:

List<string> PropertyNames = new List<string>()
{
    "Prop1",
    "Prop2"
};

if (PropertyNames.Any(p => !ModelState.IsValidField(p)))
{
    // Error
}
else
{
    // Everything is okay
}

or:

要么:

List<string> PropertyNames = new List<string>()
{
    "Prop1",
    "Prop2"
};

if (PropertyNames.All(p => ModelState.IsValidField(p)))
{
    // Everything is okay
}
else
{
    // Error
}

#3


2  

Just to add to the existing answers for this. Rather than hardcoding the property names I would use an attribute to be added along with the rest of your validation attributes along the lines of:

只是为此添加现有答案。我不会硬编码属性名称,而是使用一个属性与其他验证属性一起添加:

public class ValidationStageAttribute : Attribute
{
    public StageNumber { get; private set; }
    public ValidationStageAttribute(int stageNumber)
    {
        StageNumber = stageNumber
    }
}

Now that we can get the property names without knowledge of the model itself the partial validation can be pulled into a method (if you use it a lot, your base controller would be a good spot).

现在我们可以在不知道模型本身的情况下获取属性名称,可以将部分验证拉入方法中(如果你经常使用它,你的基本控制器将是一个好位置)。

protected bool ValidateStage(object model, int stageToValidate)
{
    var propertiesForStage = postedModel.GetType()
        .GetProperties()
        .Where(prop => prop.GetCustomAttributes(false).OfType<ValidationStageAttribute>().Where(attr => attr.StageNumber == stageToValidate));
        .Select(prop => prop.Name);

    return propertiesForStage.All(p => ModelStage.IsValidField(p));
}

Now all you'd need to do in your post action would be to call ValidateStage(postedModel, 1)

现在你在post post中需要做的就是调用ValidateStage(postedModel,1)

#4


2  

In MVC Core, this will be the equivalent of:

在MVC Core中,这将相当于:

if (ModelState.GetFieldValidationState("Name") == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Valid)
{
    // do something
}

However, I would recommend simply creating a separate view model in this instance.

但是,我建议只在这个实例中创建一个单独的视图模型。

Your partial view model could be inherited by your larger view model so you won't have to repeat yourself in code (DRY principal).

您的部分视图模型可以由更大的视图模型继承,因此您不必在代码中重复自己(DRY主体)。

It's better to avoid hard-coding the property names!

最好避免硬编码属性名称!

#1


14  

One possible solution:

一种可能的方案:

  1. Use ModelState.IsValidField(string key);

    使用ModelState.IsValidField(string key);

    if (ModelState.IsValidField("Name") && ModelState.IsValidField("Address"))
    { ... }
    

Then at the end when everything is done use:

然后在一切结束时使用:

if(ModelState.IsValid) { .. }

#2


9  

I think the most elegant way is to do it like that:

我认为最优雅的方式是这样做:

List<string> PropertyNames = new List<string>()
{
    "Prop1",
    "Prop2"
};

if (PropertyNames.Any(p => !ModelState.IsValidField(p)))
{
    // Error
}
else
{
    // Everything is okay
}

or:

要么:

List<string> PropertyNames = new List<string>()
{
    "Prop1",
    "Prop2"
};

if (PropertyNames.All(p => ModelState.IsValidField(p)))
{
    // Everything is okay
}
else
{
    // Error
}

#3


2  

Just to add to the existing answers for this. Rather than hardcoding the property names I would use an attribute to be added along with the rest of your validation attributes along the lines of:

只是为此添加现有答案。我不会硬编码属性名称,而是使用一个属性与其他验证属性一起添加:

public class ValidationStageAttribute : Attribute
{
    public StageNumber { get; private set; }
    public ValidationStageAttribute(int stageNumber)
    {
        StageNumber = stageNumber
    }
}

Now that we can get the property names without knowledge of the model itself the partial validation can be pulled into a method (if you use it a lot, your base controller would be a good spot).

现在我们可以在不知道模型本身的情况下获取属性名称,可以将部分验证拉入方法中(如果你经常使用它,你的基本控制器将是一个好位置)。

protected bool ValidateStage(object model, int stageToValidate)
{
    var propertiesForStage = postedModel.GetType()
        .GetProperties()
        .Where(prop => prop.GetCustomAttributes(false).OfType<ValidationStageAttribute>().Where(attr => attr.StageNumber == stageToValidate));
        .Select(prop => prop.Name);

    return propertiesForStage.All(p => ModelStage.IsValidField(p));
}

Now all you'd need to do in your post action would be to call ValidateStage(postedModel, 1)

现在你在post post中需要做的就是调用ValidateStage(postedModel,1)

#4


2  

In MVC Core, this will be the equivalent of:

在MVC Core中,这将相当于:

if (ModelState.GetFieldValidationState("Name") == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Valid)
{
    // do something
}

However, I would recommend simply creating a separate view model in this instance.

但是,我建议只在这个实例中创建一个单独的视图模型。

Your partial view model could be inherited by your larger view model so you won't have to repeat yourself in code (DRY principal).

您的部分视图模型可以由更大的视图模型继承,因此您不必在代码中重复自己(DRY主体)。

It's better to avoid hard-coding the property names!

最好避免硬编码属性名称!