Можно ли связать простые данные запроса с вызовом метода в NancyFX?

Я отправляю некоторые простые данные пользователя в модуль Нэнси. Я использую функцию привязки модели Нэнси, чтобы собрать данные пользователя из запроса и передать их моему методу UserService.Add(...), например:

Модуль Нэнси

Post["/add"] = parameters =>
{
    var user = this.Bind<UserDetails>();
    UserService.Add(user);
    return HttpStatusCode.OK;
};

Класс сведений о пользователе

public class UserDetails
{
    public string UserName { get; set; }
    public string Password { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
}

Обслуживание пользователей

public static void Add(UserDetails user)
{
    //Add the user     
}

Это работает и обеспечивает лаконичный синтаксис в модуле Нэнси. Однако это означает, что я вынужден создать класс передачи данных (DTO) просто для сбора полезной нагрузки запроса.

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

Это даст код, который может выглядеть примерно так:

Модуль Нэнси

Post["/add"] = parameters =>
{
    this.BindAndCall<UserService>("Add");
    return HttpStatusCode.OK;
};

Обслуживание пользователей

public static void Add(string  firstName, string  lastName, string email, string userName, string password)
{
    //Add the user     
}

person biofractal    schedule 03.04.2013    source источник


Ответы (2)


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

person Sunny Milenov    schedule 24.04.2013
comment
Я согласен насчет связи, особенно если API был общедоступным. Однако, как вы думаете, есть ли случай, когда услуга является частной? Если я реорганизую свой сервис, я также реорганизую генерацию полезной нагрузки. FWIW, я не согласен с тем, что DTO фактически бесплатны. Я думаю, что эта церемония всегда дорого обходится в долгосрочной перспективе, и мне кажется, что DTO любят церемониться. Спасибо за ваш вклад. - person biofractal; 24.04.2013

Пакет параметров — частичное решение проблемы распространения DTO

На мой взгляд, DTO - это боль, поскольку они имеют тенденцию распространяться в теневой домен. Это создает головную боль, когда вы решите реорганизовать свой сервисный слой.

Так почему бы просто не использовать типы dynamic и забыть о DTO и строго типизированных параметрах маршрута? Ну, динамические значения очень удобны, но у них есть свои проблемы, в основном то, что вы не можете передать их методам расширения. Таким образом, проще правильно представить параметры маршрута, следовательно, привязку модели, следовательно, DTO и, следовательно, спагетти и глупые шляпы.

Итак, вот частичное решение, основанное на привязке модели Нэнси. Это значительно сокращает количество церемоний на уровне маршрута и помогает сдерживать раздражающее распространение DTO.

Базовый модуль Нэнси

public class _BaseModule : NancyModule
{
    public class ParameterBag
    {
        // All the params used across all routes, GET and POST
        public string UserName { get { return this.Value; } }
        public string UserIds { get; set; }
        public string UserId { get; set; }
        public string Value { get; set; }
        public int Skip { get; set; }
        public int Take { get; set; }
    }

    public ParameterBag Params;

    public _BaseModule(): this(""){}
    public _BaseModule(string modulePath): base(modulePath)
    {
        Before += ctx =>
        {
            this.Params = this.Bind<ParameterBag>();
            return null;
        };
    }
}

Класс ParameterBag содержит все параметры, которые мне нужно связать со всеми всеми маршрутами. Поскольку Nancy будет заполнять свойства только в том случае, если найдет соответствующее поле в полезной нагрузке, вы можете просто добавить столько свойств в этот класс, сколько захотите, не заботясь о том, будут ли они использоваться данным маршрутом или нет.

Обратите внимание, как Binding происходит в хуке Before. Это означает, что каждый маршрут (производный от _BaseModule) автоматически привязывает любые совпадающие значения параметров к свойствам универсального класса ParameterBag. Никакого специального вмешательства на уровне маршрута не требуется.

Результатом этого является предоставление обработчикам маршрутов строго типизированных значений параметров, которые можно просто использовать.

Модуль Нэнси

public class UserModule : _BaseModule
{
    public UserModule()
    {
        // handlers go here
    }
}

Пример обработчика маршрута

Get["/user/{userid}/username/available"] = _ =>
{
    return Response.AsJson(new 
        { 
            // the username is a hidden value
            // the userid comes from the url
            value = Params.UserName,
            valid = UserService.UserNameAvailable(Params.UserName, Params.UserId)
        }
    );
};

Пример использования

В приведенном ниже примере используется jqBootstrapValidation. Он показывает, как трюк с привязкой работает для данных параметров, предоставленных в URL-адресе и в составе полезной нагрузки ajax (см. атрибут value).

<input 
    type="text" 
    id="username" 
    name="username" 
    placeholder="User Name" 
    data-validation-ajax-ajax="/user/@user.id/username/available" 
    data-validation-ajax-message="This name has already been taken" 
    value="@user.UserName" 
    required>
person biofractal    schedule 24.04.2013
comment
Помимо именования, я не вижу, чем это отличается от DTO. Еще есть класс, к которому привязаны все параметры. Если ваше определение для DTO не является общим классом между потребителем и службой. - person Sunny Milenov; 26.04.2013
comment
Он отличается только тем, что уменьшает количество церемоний — и это хорошая разница, имхо. Вместо создания теневого домена DTO, где у многих маршрутов есть собственный именованный DTO, единственной задачей которого является выступать в качестве источника привязки для параметров маршрута, я, по сути, создал универсальный ParameterBag, который можно просто использовать без посоветовавшись с первосвященниками. Как вы указываете, теоретически это просто DTO, но на практике я обнаружил, что наличие универсального DTO значительно снижает накладные расходы на кодирование, делая привязку всех параметров маршрута автоматической и не зависящей от маршрута. - person biofractal; 30.04.2013
comment
Согласен, но... тогда почему бы просто не использовать динамику, которую Нэнси уже использует :) - person Sunny Milenov; 01.05.2013
comment
Поскольку значения параметров dynamic не могут быть переданы в методы расширения. Они также вызывают проблемы, когда вы передаете их в обычные методы, потому что они не приводятся к типу аргумента метода, но сохраняют динамизм, пока не умрут. Это приводит к запутанным ошибкам глубоко в фрикадельках моего лучшего спагетти-кода — вы можете увидеть подозрительный тип аргумента метода, используя сигнатуру метода, и быть уверенным, но фактический тип аргумента все еще тайно динамичен во время выполнения. - person biofractal; 01.05.2013