В ASP.Net Core у вас есть несколько способов сгенерировать URL-адрес для действия контроллера, новейшими из которых являются помощники тегов.
Использование помощников по тегам для GET-запросов. asp-route используется для задания параметров маршрута. Насколько я понимаю, использование сложных объектов в запросе маршрута не поддерживается. А иногда на странице может быть много разных ссылок, указывающих на себя, возможно с незначительным добавлением URL-адреса для каждой ссылки.
Мне кажется неправильным, что любая модификация сигнатуры действия контроллера требует изменения всех помощников тегов, использующих это действие. Т.е. если добавить string query к контроллеру, нужно добавить запрос к модели и добавить 20 разных мест asp-route-query = "@Model.Query", разбросанных по cshtml-файлам. Используя этот подход, вы настраиваете код на случай ошибок в будущем.
Есть ли более элегантный способ справиться с этим? Например, какой-нибудь способ получить объект запроса? (То есть объект запроса от контроллера может быть помещен в модель и передан обратно в URL-адрес действия.)
Спасибо, я рассмотрел данные маршрута как один из вариантов. Но для этого потребуются либо параметры, считываемые как Dictionary<string, string> (значение - строковый), либо объекты, отраженные в Dictionary<string, string>. Это был бы мой запасной вариант, но я надеялся, что, может быть, есть еще лучший вариант.
Вы имели в виду что-то вроде тот? Не то чтобы я бы рекомендовал такой подход с сквозной передачей запросов.
Спасибо. Предлагаемое там решение похоже на то, что оно сработает. Описанная проблема теперь решена в ASP.Net Core, asp-route-* применяется после asp-all-route-data. Так что решение, вероятно, работало бы без расширенного словаря.





Здесь есть объяснение, которое, я думаю, вы не понимаете. Запросы GET намеренно упрощены. Они должны описывать конкретный ресурс. У них нет тел, потому что вы не должны передавать сложные объекты данных в первую очередь. Протокол HTTP разработан не так.
Кроме того, параметры строки запроса обычно должны быть необязательными. Если для идентификации ресурса требуется некоторый бит данных, он должен быть частью основного URI (то есть пути). Таким образом, пренебрежение добавлением чего-то вроде параметра query должно просто привести к возврату полного набора данных вместо некоторого подмножества, определенного query. Или, в случае чего-то вроде страницы поиска, это обычно приводит к тому, что пользователю предоставляется форма для сбора query. Другими словами, ваше действие должно учитывать отсутствие этого параметра и соответствующим образом обрабатывать эту ситуацию.
Короче говоря, нет, я полагаю, что нет никакого "элегантного" способа справиться с этим, но причина в том, что этого не должно быть. Если вы правильно проектируете свои маршруты и действия, обычно это не проблема.
Спасибо за ваш вклад. Ваш ответ о том, что, по вашему мнению, я не понимаю HTTP, что мой вариант использования неверен и что я должен все разрабатывать по-другому, не совсем то, что я ищу.
Что ж, сам факт наличия "запроса", который распространяется повсюду, указывает на плохой дизайн. HTTP не имеет состояния по какой-то причине.
Я считаю, что вы упустили суть, шаблон - не конкретный вариант использования. «Запрос» был параметром примера. В моем случае использования сегодня это идентификатор клиента и раздел. Каждая ссылка на страницу должна будет содержать их, так как я хочу избежать использования файлов cookie для хранения информации о текущем перемещении пользователя. Я также хочу избежать инкапсуляции каждой ссылки в форме для обратной передачи. Отсутствие «идентификатора клиента» не может вернуть всех клиентов, которым требуется страница со списком. Для этого мне потребуется объединить два отдельных представления в один контроллер. Похоже, много лишнего кода даром.
Кстати, почему использование параметра «запрос» указывает на плохой дизайн?
Передача что-либо повсюду - плохой дизайн, а не просто "запрос". Если к каждой ссылке нужно прикрепить вещь, это плохой дизайн. Что-то вроде «идентификатора клиента» должно быть либо связано с аутентификацией, где это может быть заявка или что-то в этом роде, либо оно должно входить в сеанс, если вам действительно нужно сохранять на протяжении всех запросов клиента. Вам не нужно устанавливать явный файл cookie, но файлы cookie необходимы для сохранения состояния, в этом весь смысл их существования.
То, что вы пытаетесь достичь здесь, в основном то же самое, что и «сеансы без файлов cookie», которые по какой-то причине являются артефактом прошлого. Они не работают и вызывают самые разные проблемы. Файлы cookie - это не плохо. Если вам нужно соответствовать GDPR, вам нужно только сообщить своим пользователям, что вы их используете, и разрешить им отказаться. Однако это нормально, если отказ означает, что ваш сайт нельзя эффективно использовать. Полностью избегать файлов cookie просто невозможно, если у вас нет очень простого, практически статичного веб-сайта.
Что ж, все, что я хотел, это передать Customerid в URL-адрес, когда администратор редактирует определенного клиента. Это быстро обострилось. Я думаю, что мы соблюдаем GDPR.
@TeddHansen: Зачем тогда спрашивать? URL-адрес MVC-esque будет /admin/customer/{customerId}/Edit (получить запрос для отображения формы редактирования, отправить запрос для сохранения изменений) или в WebApi /api/admin/customer/{customerId} GET для чтения, PUT для создания / замены данных ресурсов, публикации / патча для его изменения. Как и большинство фреймворков, ASP.NET Core склонен к шаблонам MVC / REST Api, и такая общая передача параметров запроса на самом деле не является частью этого шаблона.
Страница может содержать больше, чем просто форму редактирования для клиента, например список элементов в левой части страницы для быстрой навигации. Нам понадобится идентификатор клиента, фильтр для левого меню (результат поиска) и, возможно, информация о просмотре. /customer/{customerid}/{view}?query=Oslo. Если мы решим расширить фильтр до ?query=Oslo&category=A, нам придется обновить 20+ ссылок href на cshtml-страницах с помощью asp-route-category = "@Model.Route.Category". Отсутствие одной ссылки приведет к потере "категории", когда пользователь щелкнет ссылку. Моей целью было убрать эту зависимость в самом коде. Не о MVC как таковом.
Чтобы решить эту проблему, я хотел бы, чтобы объект запроса использовался в качестве параметров маршрута для привязки TagHelper. Это означает, что все ссылки маршрута определены только в одном месте, а не во всем решении. Изменения, внесенные в объектную модель запроса, автоматически распространяются на URL для <a asp-action>-тегов.
Преимущество этого заключается в сокращении количества мест в коде, которые нам нужно изменить при изменении сигнатуры метода для действия контроллера. Мы локализуем изменения только для модели и действия.
Я подумал, что написание тега-помощника для пользовательского asp-object-route может помочь. Я изучал цепочку Taghelpers, чтобы мой мог работать до AnchorTagHelper, но это не работает. Создание экземпляра и их вложение требует, чтобы я жестко закодировал все свойства Ядра ASP.Net AnchorTagHelper, которые могут потребовать обслуживания в будущем. Также рассматривалось использование настраиваемого метода с UrlHelper для создания URL-адреса, но тогда TagHelper не работал.
Решение, на которое я пришел, - использовать asp-all-route-data, как предлагает @ kirk-larkin, вместе с методом расширения для сериализации в Dictionary. Любой asp-all-route-* переопределит значения в asp-all-route-data.
<a asp-controller = "Test" asp-action = "HelloWorld" asp-all-route-data = "@Model.RouteParameters.ToDictionary()" asp-route-somestring = "optional override">Link</a>
ASP.Net Core может десериализовать сложные объекты (включая списки и дочерние объекты).
public IActionResult HelloWorld(HelloWorldRequest request) { }
В объекте запроса (при его использовании) обычно есть только несколько простых свойств. Но я подумал, что было бы неплохо, если бы он поддерживал и дочерние объекты. Сериализация объекта в Словаре обычно выполняется с помощью отражения, которое может быть медленным. Я подумал, что Newtonsoft.Json будет более оптимизирован, чем сам писать простой код отражения, и обнаружил, что эта реализация готова к работе:
public static class ExtensionMethods
{
public static IDictionary ToDictionary(this object metaToken)
{
// From https://geeklearning.io/serialize-an-object-to-an-url-encoded-string-in-csharp/
if (metaToken == null)
{
return null;
}
JToken token = metaToken as JToken;
if (token == null)
{
return ToDictionary(JObject.FromObject(metaToken));
}
if (token.HasValues)
{
var contentData = new Dictionary();
foreach (var child in token.Children().ToList())
{
var childContent = child.ToDictionary();
if (childContent != null)
{
contentData = contentData.Concat(childContent)
.ToDictionary(k => k.Key, v => v.Value);
}
}
return contentData;
}
var jValue = token as JValue;
if (jValue?.Value == null)
{
return null;
}
var value = jValue?.Type == JTokenType.Date ?
jValue?.ToString("o", CultureInfo.InvariantCulture) :
jValue?.ToString(CultureInfo.InvariantCulture);
return new Dictionary { { token.Path, value } };
}
}В другом ответе я нашел способ предоставить объект запроса через модель.
Из статьи SO @tseng я нашел меньшее решение. Он не использует объект запроса в модели, но сохраняет все параметры маршрута, если это явно не переопределено. Он не позволит вам указывать маршрут через объект запроса, что в любом случае чаще всего не то, что вам нужно. Но это решило проблему в OP.
<a asp-controller = "Test" asp-action = "HelloWorld" asp-all-route-data = "@Context.GetQueryParameters()" asp-route-somestring = "optional override">Link</a>
Для этого требуется метод расширения для преобразования параметров запроса в словарь.
public static Dictionary GetQueryParameters(this HttpContext context)
{
return context.Request.Query.ToDictionary(d => d.Key, d => d.Value.ToString());
}
Вскоре asp-all-route-data = "@Context.Request.Query.ToDictionary(d => d.Key, d => d.Value.ToString())"
asp-all-route-dataможет помочь с вашей конкретной проблемой здесь. Я бы не стал так пренебрегать советом Криса (он твердый), но я понимаю, что иногда изменения, необходимые для «правильной работы», не столь практичны.