Как проверить тело запроса json как действительный json в ядре asp.net - PullRequest
1 голос
/ 14 марта 2019

В ядре asp.net 2.1, когда действие контроллера установлено как:

    [HttpPost]
    public JsonResult GetAnswer(SampleModel question)
    {               
        return Json(question.Answer);
    }

где SampleModel определяется как:

public class SampleModel
{
    [Required]
    public string Question { get; set; }

    public string Answer { get; set; }
}

это все еще считается действительным запросом:

{
  "question": "some question",
  "question": "some question 2",
  "answer": "some answer"
}

В контроллере я вижу, что второй вопрос - это значение модели, а модель действительна.

Вопрос в том, как проверить только тело запроса как допустимый JSON еще до привязки модели?

1 Ответ

2 голосов
/ 14 марта 2019

Согласно ответу Тимоти Шилдса , трудно сказать, что это был бы неверный json, если бы мы дублировали ключи свойств.

А при использовании ASP.NET Core 2.1 он вообще не скинет.

Начиная с 12.0.1, Newtonsoft.Json имеет настройки DuplicatePropertyNameHandling . Он выдаст, если мы установим DuplicatePropertyNameHandling.Error и передадим дублированное свойство. Таким образом, самый простой способ, которым я могу придумать, - это создать пользовательское связующее для моделей. Мы могли бы десериализовать JSON и изменить ModelState, если он выбрасывает.

Fristly, установите последнюю версию Newtonsoft.Json:

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
  </ItemGroup>

и затем зарегистрируйте опцию JsonLoadSettings в качестве одноэлементной службы для последующего повторного использования:

services.AddSingleton<JsonLoadSettings>(sp =>{
    return new JsonLoadSettings { 
        DuplicatePropertyNameHandling =  DuplicatePropertyNameHandling.Error,
    };
});

Теперь мы можем создать привязку пользовательской модели для работы с дублированными свойствами:

public class XJsonModelBinder: IModelBinder
{
    private JsonLoadSettings _loadSettings;
    public XJsonModelBinder(JsonLoadSettings loadSettings)
    {
        this._loadSettings = loadSettings;
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
        var modelName = bindingContext.BinderModelName?? "XJson";
        var modelType = bindingContext.ModelType;

        // create a JsonTextReader
        var req = bindingContext.HttpContext.Request;
        var raw= req.Body;
        if(raw == null){ 
            bindingContext.ModelState.AddModelError(modelName,"invalid request body stream");
            return Task.CompletedTask;
        }
        JsonTextReader reader = new JsonTextReader(new StreamReader(raw));

        // binding 
        try{
            var json= (JObject) JToken.Load(reader,this._loadSettings);
            var o  = json.ToObject(modelType);
            bindingContext.Result = ModelBindingResult.Success(o);
        }catch(Exception e){
            bindingContext.ModelState.AddModelError(modelName,e.ToString()); // you might want to custom the error info
            bindingContext.Result = ModelBindingResult.Failed();
        }
        return Task.CompletedTask;
    }
}

Чтобы разрешить чтение Request.Body несколько раз, мы также можем создать пустышку Filter:

public class EnableRewindResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.HttpContext.Request.EnableRewind();
    }
    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

Наконец, украсьте метод действия с помощью [ModelBinder(typeof(XJsonModelBinder))] и EnableRewindResourceFilter:

    [HttpPost]
    [EnableRewindResourceFilter]
    public JsonResult GetAnswer([ModelBinder(typeof(XJsonModelBinder))]SampleModel question)
    {               
        if(ModelState.IsValid){
            return Json(question.Answer);
        }
        else{
            // ... deal with invalid state
        }
    }

Демо:

enter image description here

...