Как я могу создать настраиваемую привязку параметров для заявки? - PullRequest
0 голосов
/ 25 июня 2019

Я хочу иметь возможность предоставить заявку от текущего пользователя прямо в параметрах контроллера.Так что я могу писать модульные тесты, не касаясь магии ClaimPrincipal.

Как [FromUri] или [FromBody], может быть, [FromClaim]?

Я попытался реализовать CustomModelProvider, как указано в этой документацииот Microsoft: https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-2.2

Но я не знаю, как мне предоставить ClaimsPrincipal или List.Кроме того, ValueProvider возвращает строку, поэтому я не уверен, что это на самом деле возможно.

Это моя попытка ClaimModelBinder

public class ClaimModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));

        var modelName = bindingContext.ModelName;

        // Try to fetch the value of the argument by name
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

        if (valueProviderResult == ValueProviderResult.None) return Task.CompletedTask;

        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        var value = valueProviderResult.FirstValue;

        // TODO: Unsure, how to continue after this.

        // Check if the argument value is null or empty
        if (string.IsNullOrEmpty(value)) return Task.CompletedTask;

        int id = 0;
        if (!int.TryParse(value, out id))
        {
            // Non-integer arguments result in model state errors
            bindingContext.ModelState.TryAddModelError(
                                    modelName,
                                    "Author Id must be an integer.");
            return Task.CompletedTask;
        }

        // Model will be null if not found, including for 
        // out of range id values (0, -3, etc.)
        bindingContext.Result = ModelBindingResult.Success(null);
        return Task.CompletedTask;
    }
}

1 Ответ

2 голосов
/ 25 июня 2019

Можете ли вы предоставить источник для "создания ClaimsPrincipal для тестирования гораздо проще и правильнее, чем то, что вы пытаетесь сделать"?

Источник - я.Что касается того, почему я это сказал, то оно основано на понимании того, как написано ядро ​​ASP NET Core , как я продемонстрирую ниже.

Чтобы ответить на ваш вопрос, у Controller есть свойство User для доступа к утверждениям, нет необходимости писать Model Binder для доступа к утверждениям, когда свойство User уже существует, если, конечно, вы не можете получить доступ к утверждениям этого пользователясобственность из-за вашей логики претензий быть другим.Но вы не делали таких упоминаний.

"Я хочу иметь возможность предоставить заявку от текущего пользователя непосредственно в параметрах контроллера. Чтобы я мог писать модульные тесты, не касаясьПринцип магии Claim. "

Я интерпретировал это как

" Я хочу написать модульные тесты для моего контроллера, который имеет логику с использованием принципала утверждений, но я не знаю, какпредоставить поддельный принципал утверждений, поэтому я собираюсь избежать этого и передать вместо него параметр метода "

ClaimsPrincipal может быть не магическим, как показано ниже.

  • Controllerимеет свойство User, но это только Get. Магия
  • HttpContext имеет свойство User, которое является Get и Set ( Nice ), но Controller.HttpContext is Get only ( Not So Nice)
  • Controller имеет свойство ControllerContext, которое является Get и Set, ControllerContext имеет свойство HttpContext, которое является Get и Set. Джекпот!

Это исходный код из ControllerBase, из которого Controller и ApiController происходят,

public abstract class ControllerBase
{
    /* simplified below */
    public ControllerContext ControllerContext
    {
            get => _controllerContext;
            set => _controllerContext = value;
    }
    /* ... */
    public HttpContext HttpContext => ControllerContext.HttpContext;
    /* ... */
    public ClaimsPrincipal User => HttpContext?.User;
}

Как вы видите здесь, пользователь, к которому вы обращаетесь, - это удобный инструмент, который в конечном итоге обращается к ControllerContext.HttpContext.User.Зная эту информацию, вы можете выполнить модульное тестирование контроллера, который использует ClaimsPrincipal, следующим образом.

// Create a principal according to your requirements, following is exemplary
var principal = new ClaimsPrincipal(new ClaimsIdentity(new []
{
    // you might have to use ClaimTypes.Name even for JWTs issued as sub.
    new Claim(JwtRegisteredClaimNames.Sub, "1234"), 
    new Claim(JwtRegisteredClaimNames.Iss, "www.example.com"),
}, "Bearer"));

var httpContext = new DefaultHttpContext();
httpContext.User = principal;

// Fake anything you want
httpContext.Request.Headers = /* ... */

var controller = new ControllerUnderTest(...);
controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = httpContext;

// Test the action, no need to pass claims as parameters because the User property is set
var result = controller.ActionThatUsesUserClaims(...);
Assert.Something(result, expected);

Именно так работает ASP NET Core при каждом получении реального веб-запроса.Это буквально делает вышеперечисленное, чтобы сделать контроллер работоспособным и готовым к использованию.

Все вышеперечисленное является частью public ASP NET Core API и не подвержено критическим изменениям безосновная версия Bumb, поэтому они безопасны в использовании.Фактически, это одна из вещей, которая отличает ASP Net Core от старого ASP NET MVC, который был кошмаром для тестирования, поскольку он не раскрывал ничего из вышеперечисленного публично.

Сказав все это, дляпочему-то я упустил из виду, что если вам действительно нужно написать связыватель модели для предоставления утверждений, введите HTTPContextAccessor .Но для этого необходимо проверить тип параметров метода и выполнение ветвления.Одна ветвь будет связывать свойства из провайдера значений, а другая - из HttpContext.Но зачем беспокоиться, если вы можете сделать выше с рефакторингом 0?

...