C# 有选择地将要绑定的模型字段列为白名单
(我意识到这个问题与以前的问题非常相似,但我的情况略有不同,现在可能有更好的解决方案。) 我们公司销售基于网络的软件,最终用户可以非常方便地进行配置。这种灵活性的本质意味着我们必须在运行时完成许多通常在编译时完成的事情 关于谁拥有对大多数事物的读或读/写权限,有一些相当复杂的规则 例如,以我们想要创建的模型为例:C# 有选择地将要绑定的模型字段列为白名单,c#,asp.net-mvc,model,C#,Asp.net Mvc,Model,(我意识到这个问题与以前的问题非常相似,但我的情况略有不同,现在可能有更好的解决方案。) 我们公司销售基于网络的软件,最终用户可以非常方便地进行配置。这种灵活性的本质意味着我们必须在运行时完成许多通常在编译时完成的事情 关于谁拥有对大多数事物的读或读/写权限,有一些相当复杂的规则 例如,以我们想要创建的模型为例: using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace j6.Business.Site.Models
{
public class ModelBindModel
{
[Required]
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string FirstName { get; set; }
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string MiddleName { get; set; }
[Required]
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string LastName { get; set; }
[Required]
[Whitelist(ReadAccess = User.CanReadSalary, WriteAccess = User.CanWriteSalary)]
public string Salary { get; set; }
[Required]
[Whitelist(ReadAccess = User.CanReadSsn, WriteAccess = User.CanWriteSsn)]
public string Ssn { get; set; }
[Required]
public string SirNotAppearingOnThisPage { get; set; }
}
}
在控制器中,手动“解除”绑定并不困难
var resetValue = null;
modelState.Remove(field);
pi = model.GetType().GetProperty(field);
if (pi == null)
{
throw new Exception("An exception occured in ModelHelper.RemoveUnwanted. Field " +
field +
" does not exist in the model " + model.GetType().FullName);
}
// Set the default value.
pi.SetValue(model, resetValue, null);
使用HTML帮助程序,我可以轻松访问模型元数据,并禁止呈现用户无权访问的任何字段
关键:我不知道如何访问控制器本身中的任何位置的模型元数据以防止过度发布
请注意,使用[Bind(Include…)不是一个功能性解决方案,至少在没有额外支持的情况下是如此。要包含的属性依赖于运行时(而非编译时),排除该属性不会将其从验证中删除
ViewData.Model
为null
ViewData.ModelMetaData
为null
[AllowAnonymous]
[HttpPost]
// [Bind(Exclude = "Dummy1" + ",Dummy2")]
public ViewResult Index(ModelBindModel dto)
{
zzz.ModelHelper.RemoveUnwanted(ModelState, dto, new string[] {"Salary", "Ssn"});
ViewBag.Method = "Post";
if (!ModelState.IsValid)
{
return View(dto);
}
return View(dto);
}
关于如何从控制器访问模型元数据有什么建议吗?还是在运行时将属性列为白名单的更好方法
更新: 我从这个相当优秀的资源中借用了一页:
使用如下所示的模型:
[Required]
[WhiteList(ReadAccessRule = "Nope", WriteAccessRule = "Nope")]
public string FirstName { get; set; }
[Required]
[WhiteList(ReadAccessRule = "Database.CanRead.Key", WriteAccessRule = "Database.CanWrite.Key")]
public string LastName { get; set; }
班级:
public class WhiteList : Attribute
{
public string ReadAccessRule { get; set; }
public string WriteAccessRule { get; set; }
public Dictionary<string, object> OptionalAttributes()
{
var options = new Dictionary<string, object>();
var canRead = false;
if (ReadAccessRule != "")
{
options.Add("readaccessrule", ReadAccessRule);
}
if (WriteAccessRule != "")
{
options.Add("writeaccessrule", WriteAccessRule);
}
if (ReadAccessRule == "Database.CanRead.Key")
{
canRead = true;
}
options.Add("canread", canRead);
options.Add("always", "be there");
return options;
}
}
公共类白名单:属性
{
公共字符串ReadAccessRule{get;set;}
公共字符串WriteAccessRule{get;set;}
公共字典OptionalAttributes()
{
var options=newdictionary();
var canRead=false;
如果(ReadAccessRule!=“”)
{
添加(“readaccessrule”,readaccessrule);
}
如果(WriteAccessRule!=“”)
{
添加(“writeaccessrule”,writeaccessrule);
}
if(ReadAccessRule==“Database.CanRead.Key”)
{
canRead=true;
}
选项。添加(“可读取”,可读取);
选项。添加(“始终”、“在那里”);
返回选项;
}
}
并将这些行添加到链接中提到的MetadataProvider类中:
var whiteListValues = attributes.OfType<WhiteList>().FirstOrDefault();
if (whiteListValues != null)
{
metadata.AdditionalValues.Add("WhiteList", whiteListValues.OptionalAttributes());
}
var whiteListValues=attributes.OfType().FirstOrDefault();
if(whiteListValues!=null)
{
metadata.AdditionalValues.Add(“白名单”,whiteListValues.OptionalAttributes());
}
最后,系统的核心:
public static void DemandFieldAuthorization<T>(ModelStateDictionary modelState, T model)
{
var metaData = ModelMetadataProviders
.Current
.GetMetadataForType(null, model.GetType());
var props = model.GetType().GetProperties();
foreach (var p in metaData.Properties)
{
if (p.AdditionalValues.ContainsKey("WhiteList"))
{
var whiteListDictionary = (Dictionary<string, object>) p.AdditionalValues["WhiteList"];
var key = "canread";
if (whiteListDictionary.ContainsKey(key))
{
var value = (bool) whiteListDictionary[key];
if (!value)
{
RemoveUnwanted(modelState, model, p.PropertyName);
}
}
}
}
}
publicstaticvoiddemandFieldAuthorization(ModelStateDictionary modelState,T model)
{
var metaData=modelmataproviders
现在的
.GetMetadataForType(null,model.GetType());
var props=model.GetType().GetProperties();
foreach(metaData.Properties中的var p)
{
if(p.AdditionalValues.ContainsKey(“白名单”))
{
var whiteListDictionary=(Dictionary)p.AdditionalValues[“WhiteList”];
var key=“canread”;
if(whiteListDictionary.ContainsKey(键))
{
var值=(bool)whiteListDictionary[key];
如果(!值)
{
移除不需要的(modelState、model、p.PropertyName);
}
}
}
}
}
大约一年前,我写了一个扩展方法,从那以后,它对我有好几次的帮助。我希望这对你有所帮助,尽管这可能不是你的全部解决方案。它基本上只允许对发送给控制器的表单上的字段进行验证:
internal static void ValidateOnlyIncomingFields(this ModelStateDictionary modelStateDictionary, FormCollection formCollection)
{
IEnumerable<string> keysWithNoIncomingValue = null;
IValueProvider valueProvider = null;
try
{
// Transform into a value provider for linq/iteration.
valueProvider = formCollection.ToValueProvider();
// Get all validation keys from the model that haven't just been on screen...
keysWithNoIncomingValue = modelStateDictionary.Keys.Where(keyString => !valueProvider.ContainsPrefix(keyString));
// ...and clear them.
foreach (string errorKey in keysWithNoIncomingValue)
modelStateDictionary[errorKey].Errors.Clear();
}
catch (Exception exception)
{
Functions.LogError(exception);
}
}
当然,在ActionResult声明中还需要一个FormCollection参数:
public ActionResult MyAction (FormCollection formCollection) {
概括一下我对你问题的解释:
- 现场访问是动态的;有些用户可能能够写入字段,有些则不能
- 您有一个在视图中控制此操作的解决方案
- 您希望防止恶意表单提交发送受限属性,然后模型绑定器将这些属性分配给您的模型
// control general access to the method with attributes
[HttpPost, SomeOtherAttributes]
public ViewResult Edit( Foo model ){
// presumably, you must know the user to apply permissions?
DemandFieldAuthorization( model, user );
// if the prior call didn't throw, continue as usual
if (!ModelState.IsValid){
return View(dto);
}
return View(dto);
}
private void DemandFieldAuthorization<T>( T model, User user ){
// read the model's property metadata
// check the user's permissions
// check the actual POST message
// throw if unauthorized
}
//使用属性控制对方法的常规访问
[HttpPost,SomeOtherAttributes]
公共视图结果编辑(Foo模型){
//大概,您必须知道要应用权限的用户?
DemandFieldAuthorization(型号、用户);
//如果先前的呼叫没有抛出,请像往常一样继续
如果(!ModelState.IsValid){
返回视图(dto);
}
返回视图(dto);
}
私有void DemandFieldAuthorization(T模型,用户){
//读取模型的属性元数据
//检查用户的权限
//检查实际的POST消息
//未经授权就扔
}
+1-问题很好。如果没有其他解决方案可用,我想您可以评估用户和模型,作为POST/PUT中任何不匹配的第一步。看起来您的模型包含了足够的元数据来进行确定。当然,该评估逻辑将驻留在一个helper类中,因此它只会向每个操作方法添加1-2行,例如,ModelHelper.DemandFieldAuthorization(model,user)
@TimMedora--您能否详细说明“评估用户和模型作为POST/PUT的第一步”?我不太清楚你说的是什么意思。当然,给我一分钟,我会把它写下来作为答案。你是从哪里得到这个的?@Moby'sStuntDouble--我请了一天假,所以我还没有尝试这两种解决方案。当我到达某个地方时,我会更新答案。如果我没有更好的解决方案,我会走这条路。谢谢你的选择。我会非常感兴趣地研究你的解决方案,谢谢分享。希望你的应用程序很棒。我花了一段时间让所有的部分都正常工作,但我终于找到了一些东西。非常感谢。
// control general access to the method with attributes
[HttpPost, SomeOtherAttributes]
public ViewResult Edit( Foo model ){
// presumably, you must know the user to apply permissions?
DemandFieldAuthorization( model, user );
// if the prior call didn't throw, continue as usual
if (!ModelState.IsValid){
return View(dto);
}
return View(dto);
}
private void DemandFieldAuthorization<T>( T model, User user ){
// read the model's property metadata
// check the user's permissions
// check the actual POST message
// throw if unauthorized
}