2013-04-10 78 views
2

(我知道這個問題是非常相似的How to whitelist/blacklist child object fields in the ModelBinder/UpdateModel method?但我的情況是不同的,有可能是現在可以更好的解決辦法,這不是呢。)選擇性白名單模型字段到綁定

我們公司銷售基於web該軟件可以由最終用戶極爲配置。這種靈活性的性質意味着我們必須在編譯時通常在運行時間上執行一些操作。

關於誰讀取或讀取/寫入大部分內容都有一些相當複雜的規則。

舉例來說,採取這種模式,我們想創造:

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幫助程序,我可以輕鬆訪問模型元數據並抑制呈現用戶無權訪問的任何字段。

踢球者:我不知道如何訪問模型元數據本身的任何地方,以防止過度發佈。

請注意,使用[綁定(包括...)]不是一個功能性解決方案,至少不是沒有額外的支持。要包含的屬性是運行時(不是編譯時間)相關的,排除屬性而不是將其從驗證中移除。

ViewData.Modelnull
ViewData.ModelMetaDatanull

[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); 
} 

如何從控制器訪問模型元數據有什麼建議?或者在運行時將白名單列入白名單的更好方法?


更新:

我借了頁面從這個相當優秀的資源:
http://www.dotnetcurry.com/ShowArticle.aspx?ID=687

與模型,看起來像這樣:

[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; 
    } 
} 

而且這些行添加到鏈接中提到的MetadataProvider類:

var whiteListValues = attributes.OfType<WhiteList>().FirstOrDefault(); 

if (whiteListValues != null) 
{ 
    metadata.AdditionalValues.Add("WhiteList", 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); 
       } 
      } 
     } 
    } 
} 
+0

+1 - 優秀的問題。如果沒有其他解決方案可用,我想你可以評估用戶和模型,作爲POST/PUT中第一步的任何不匹配。它看起來像你的模型包含足夠的元數據來做出決定。當然,該評估邏輯將駐留在助手類中,因此它只會爲每個操作方法添加1-2行,例如'ModelHelper.DemandFieldAuthorization(model,user)' – 2013-04-10 21:27:02

+0

@TimMedora - 您可否詳細說明「評估用戶和模型是POST/PUT的第一步」?我不太確定你的意思。 – 2013-04-10 21:33:25

+0

當然,給我一分鐘,我會把它寫出來作爲答案。 – 2013-04-10 21:34:30

回答

2

總結一下我對你的問題的解釋:

  • 字段訪問是動態的;一些用戶可能能夠寫入字段,而有些用戶可能不能。
  • 你有一個解決方案來控制這個視圖。
  • 您想防止惡意表單提交發送限制屬性,然後模型聯編程序將分配給您的模型。

也許像這樣?

// 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 
} 
+0

花了我一段時間纔得到所有的作品,但我終於找到了一些東西。謝謝。 – 2013-04-13 02:33:22

1

我寫了一個擴展方法一年多以前,有自那以後,我好幾次站在了我的身邊。我希望這有一些幫助,儘管可能不是您的完整解決方案。它本質上只允許對已經存在向控制器發送表單上的字段的驗證:

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); 
    } 

} 

用法:

ModelState.ValidateOnlyIncomingFields(formCollection); 

而且你需要在你的ActionResult聲明中的FormCollection參數的課程:

public ActionResult MyAction (FormCollection formCollection) { 
+1

如果我沒有更好的解決方案,我會走這條路。感謝您的選擇。 – 2013-04-13 02:32:40

+0

我將以極大的興趣檢查您的解決方案,謝謝分享。希望你的應用真棒。 – 2013-04-13 03:45:17

相關問題