Jarvis.WebPages
2.0.0.6
See the version list below for details.
dotnet add package Jarvis.WebPages --version 2.0.0.6
NuGet\Install-Package Jarvis.WebPages -Version 2.0.0.6
<PackageReference Include="Jarvis.WebPages" Version="2.0.0.6" />
<PackageVersion Include="Jarvis.WebPages" Version="2.0.0.6" />
<PackageReference Include="Jarvis.WebPages" />
paket add Jarvis.WebPages --version 2.0.0.6
#r "nuget: Jarvis.WebPages, 2.0.0.6"
#:package Jarvis.WebPages@2.0.0.6
#addin nuget:?package=Jarvis.WebPages&version=2.0.0.6
#tool nuget:?package=Jarvis.WebPages&version=2.0.0.6
Jarvis.WebPages
Biblioteca de componentes TagHelper para construção de formulários em Razor Pages e MVC.
Instalação
Requer .NET 10.
dotnet add package Jarvis.WebPages
Registre os TagHelpers no _ViewImports.cshtml:
@addTagHelper *, Jarvis.WebPages
Adicione o script de máscaras e formatação e o CSS dos componentes no layout:
<link rel="stylesheet" href="/_content/Jarvis.WebPages/css/style.css" />
<script src="/_content/Jarvis.WebPages/js/init.js"></script>
Cores e tema
O CSS dos componentes não traz paleta própria: cada cor procura primeiro a variável do tema do app e, na falta dela, cai num neutro. Basta o app definir as suas para todos os componentes acompanharem.
:root {
--c-primary: #0d6efd;
--c-surface: #fff;
--c-text: #212529;
--c-text-muted: #6c757d;
--c-border: #dee2e6;
--c-border-soft: #f1f3f5;
--radius: .375rem;
}
| Variável do app | Neutro | Onde aparece |
|---|---|---|
--c-primary |
#212529 |
Destaques: item selecionado, dia de hoje, contador, fab, atalhos |
--c-surface |
#fff |
Fundo dos painéis, do calendário e das células |
--c-text |
#212529 |
Texto dos componentes |
--c-text-muted |
#6c757d |
Placeholder, dias da semana, texto secundário |
--c-border |
#dee2e6 |
Bordas e linhas da grade do calendário |
--c-border-soft |
#f1f3f5 |
Divisórias internas e dias fora do mês |
--radius |
.375rem |
Raio de painéis e botões |
Para mudar só um componente, redefina a variável --jarvis-* correspondente no elemento — é o que
os atributos color do Fab, do Calendar e o asp-add-color dos selects fazem por dentro.
.minha-pagina .jarvis-calendar {
--jarvis-primary: #e91e63;
}
Componentes
Propriedades Comuns (BaseTagHelper)
| Propriedade | Tipo | Descrição |
|---|---|---|
asp-for |
ModelExpression |
Binding com a propriedade do modelo |
asp-readonly |
bool |
Torna o campo somente leitura |
asp-disabled |
bool |
Desabilita o campo |
asp-name |
string |
Sobrescreve o nome do campo |
Todos os componentes geram automaticamente: label, input, texto descritivo (via [Display(Description)]) e mensagem de validação.
Como identificar o componente
As funções públicas de JavaScript — jarvisForm.set, jarvisForm.get, jarvisForm.clear,
jarvisSelect.select, jarvisSelect.rebuild, jarvisCalendar.select, jarvisCalendar.clear e
jarvisCalendar.rebuild — recebem o alvo no primeiro parâmetro e aceitam quatro formas:
| Forma | Exemplo |
|---|---|
| Elemento | jarvisSelect.rebuild(document.querySelector('#cidades'), items) |
| Seletor CSS | jarvisSelect.rebuild('#div-cidades [data-jarvis-multi-select]', items) |
Id, sem o # |
jarvisCalendar.rebuild('agenda', data) |
| Nome do campo | jarvisSelect.rebuild('CidadesDaUfIds', items) |
O nome do campo é a propriedade do asp-for, com ou sem o prefixo do modelo — CidadesDaUfIds e
Model.CidadesDaUfIds chegam no mesmo campo. Vale para o FormSelect e o FormMultiSelect, que
gravam o nome em data-jarvis-select-name.
O alvo também não precisa ser o componente em si: pode ser um elemento acima (a div da coluna, o
div que envolve o calendário) ou abaixo dele. A busca sobe pelo closest e, se não achar, desce
pelo querySelector. Então isto tudo aponta para o mesmo campo:
jarvisSelect.rebuild('CidadesDaUfIds', items);
jarvisSelect.rebuild('#div-model-cidadesdaufids', items);
jarvisSelect.rebuild('#div-model-cidadesdaufids [data-jarvis-multi-select]', items);
Quando nada é encontrado, a função sai sem fazer nada — não lança erro.
Lendo e escrevendo os valores
jarvisForm altera qualquer campo da biblioteca pelo mesmo caminho da digitação: o valor é escrito,
o componente formata, valida os limites e chama o asp-on-change (ou o asp-on-complete).
jarvisForm.set('Nome', 'Maria');
jarvisForm.set('Cpf', '45054532005'); // vira 450.545.320-05
jarvisForm.set('Valor', 1234.5); // vira R$ 1.234,50
jarvisForm.set('DataNascimento', '1988-11-03');
jarvisForm.set('HoraInicio', '14:30');
jarvisForm.set('Agendamento', '2026-08-16T14:30');
jarvisForm.set('Uf', 'PE');
jarvisForm.set('CidadesIds', ['1', '5']);
jarvisForm.get('Valor'); // "1234,50" (o mesmo que vai no post)
jarvisForm.get('DataNascimento'); // "1988-11-03"
jarvisForm.get('CidadesIds'); // ["1", "5"]
jarvisForm.clear('Uf');
| Função | Descrição |
|---|---|
set |
Escreve o valor e reaplica a formatação. Devolve false se o campo não existe |
get |
Devolve o valor lógico, não o texto exibido |
clear |
Esvazia o campo, o mesmo que set(alvo, '') |
O que entra e o que sai de cada componente:
| Componente | set aceita |
get devolve |
|---|---|---|
FormText, FormTextArea, FormPassword |
texto | texto |
FormMask |
dígitos ou o valor já formatado | texto com a máscara |
FormNumeric |
número ou texto | valor do hidden |
FormDate |
yyyy-MM-dd ou dd/MM/yyyy |
yyyy-MM-dd |
FormTime |
HH:mm |
HH:mm |
FormDateTime |
yyyy-MM-ddTHH:mm |
yyyy-MM-ddTHH:mm |
FormSelect |
valor da opção | valor selecionado |
FormMultiSelect |
valor ou array de valores | array dos marcados |
FormDropzone |
File ou array de File |
array de File |
FormCheck, FormSwitch |
true, 1 ou o texto deles |
true ou false |
Valores fora dos limites de asp-min/asp-max são recusados pelo componente, igual à digitação.
No FormMultiSelect a lista informada substitui o que estava marcado. O alvo segue as formas de
Como identificar o componente.
Validação
As anotações do modelo valem no cliente sem configuração nenhuma. Quando o jQuery Validate está na página, a biblioteca acerta sozinha os três pontos em que ele não daria conta:
- Campos escondidos. O valor do
FormSelect, doFormMultiSelect, doFormNumerice doFormDropzonenão fica no elemento que aparece na tela, e o validador ignora:hiddenpor padrão. A biblioteca tira esses campos da lista de ignorados e coloca a validação em quem leva o nome da propriedade, para a mensagem cair no lugar certo. - Formato de data e de número. As regras
dateenumberdo jQuery Validate só entendem o formato americano, e recusariam16/08/2026e1234,50. Elas passam a aceitar também o formato brasileiro — a data com ou sem hora, o número com vírgula ou ponto. - Valor escrito por código. Escolher no painel, marcar uma opção ou chamar
jarvisForm.setnão dispara evento. A biblioteca emite umchangee revê o campo, então o erro some assim que o valor é corrigido — o erro só é reavaliado se já estiver na tela.
A borda vermelha aparece no elemento visível de cada componente, mesmo quando a classe de erro fica no campo escondido.
Nada disso vaza para o resto da página. As regras trocadas desviam para a implementação original
quando o campo não é de um componente da biblioteca, e a lista de ignorados do projeto é preservada —
a biblioteca só acrescenta a exceção dos campos escondidos que guardam o valor. Um <input class="date">
de outra biblioteca continua validando como antes.
No FormMultiSelect, use [MinLength] em vez de [Required]. São dois motivos: no servidor uma
coleção vazia não é nula, então o [Required] passaria; e no cliente o unobtrusive não aplica o
required em checkbox, porque para ele checkbox marcado é o aceite de um bool. O [MinLength] vale
nos dois lados, contando quantos itens estão marcados:
[MinLength(1, ErrorMessage = "Selecione ao menos uma cidade")]
public List<int> CidadesIds { get; set; } = [];
FormText
Campo de texto simples.
<form-text asp-for="Nome" asp-col-css="col-md-6" asp-placeholder="Digite o nome" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-input-css |
string |
— | Classe CSS adicional no input |
asp-placeholder |
string |
— | Placeholder do campo |
FormTextArea
Campo de texto multilinha.
<form-text-area asp-for="Observacao" asp-col-css="col-md-12" asp-placeholder="Descreva..." />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-input-css |
string |
— | Classe CSS adicional no input |
asp-placeholder |
string |
— | Placeholder do campo |
FormPassword
Campo de senha.
<form-password asp-for="Senha" asp-col-css="col-md-6" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-input-css |
string |
— | Classe CSS adicional no input |
FormCheck
Caixa de marcação para uma propriedade bool. O texto ao lado sai do [Display(Name)].
<form-check asp-for="AceitoOsTermos" />
<form-check asp-for="Bloqueado" asp-col-css="col-md-6" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-input-css |
string |
— | Classe CSS adicional no elemento clicável |
O texto fica ao lado do controle, dentro do mesmo label, e não acima como nos demais campos —
por isso o asp-input-css cai nesse label, e não no input, que é invisível.
Junto do campo vai um hidden com false, então a propriedade volta para false quando nada está
marcado — sem ele o post não enviaria nada e o valor anterior permaneceria.
Com asp-disabled o campo não vai no post. Com asp-readonly ele não pode ser alterado, mas o
valor continua sendo enviado: o readonly não vale para checkbox no navegador, então o componente
desabilita a caixa e repete o valor num hidden.
FormSwitch
Mesmo comportamento do FormCheck, com a aparência de interruptor. As propriedades e as regras de
asp-readonly e asp-disabled são as mesmas.
<form-switch asp-for="ComAnestesia" />
<form-switch asp-for="Ativo" asp-col-css="col-md-6" />
Use o FormSwitch quando o campo liga ou desliga um comportamento, e o FormCheck quando é um
aceite ou um item de lista.
FormDate
Campo de data com máscara dd/mm/aaaa e calendário próprio da biblioteca — sem o picker nativo do
navegador, que muda de cara em cada sistema. O valor é enviado como texto, então o model binding e a
validação continuam iguais aos dos demais componentes.
<form-date asp-for="DataNascimento" asp-col-css="col-md-4" />
<form-date asp-for="DataNascimento" asp-col-css="col-md-4" asp-show-picker="false" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-input-css |
string |
— | Classe CSS adicional no input |
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-min |
DateTime? |
— | Data mínima permitida |
asp-max |
DateTime? |
— | Data máxima permitida |
asp-on-change |
string |
— | Nome da função JS chamada ao alterar a data |
asp-show-picker |
bool |
true |
Exibe o botão que abre o calendário |
O botão ao lado do campo abre o calendário: setas para trocar de mês, o dia de hoje contornado e o
dia escolhido preenchido. Datas fora de asp-min/asp-max ficam desabilitadas, e o painel abre
para cima quando não há espaço abaixo. Com asp-show-picker="false" o campo fica só com a máscara.
Digitar também funciona: a máscara aceita apenas números, monta dd/mm/aaaa e descarta o que não
forma uma data válida ou cai fora dos limites.
Exemplo com asp-on-change
<form-date asp-for="DataInicio" asp-min="DateTime.Today" asp-on-change="onDataInicioChange" />
<script>
function onDataInicioChange(value) {
// value = "2026-08-15" (formato ISO), ou "" quando o campo é limpo
}
</script>
A função é chamada quando a data fica completa, tanto digitando quanto escolhendo no calendário.
FormTime
Campo de hora com máscara hh:mm e painel com as listas de hora e minuto. Aceita TimeSpan,
TimeOnly e DateTime no modelo.
<form-time asp-for="HoraInicio" asp-col-css="col-md-3" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-input-css |
string |
— | Classe CSS adicional no input |
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-min |
TimeSpan? |
— | Hora mínima permitida |
asp-max |
TimeSpan? |
— | Hora máxima permitida |
asp-minute-step |
int |
5 |
Intervalo entre os minutos da lista |
asp-on-change |
string |
— | Nome da função JS chamada ao alterar a hora |
asp-show-picker |
bool |
true |
Exibe o botão que abre as listas |
<form-time asp-for="HoraFim" asp-min="new TimeSpan(8, 0, 0)" asp-max="new TimeSpan(18, 0, 0)" asp-minute-step="15" />
Escolher a hora mantém o painel aberto; escolher o minuto fecha. O asp-on-change recebe "14:30",
ou "" quando o campo é limpo.
FormDateTime
Campo de data e hora com máscara dd/mm/aaaa hh:mm. O painel traz o calendário e as listas de hora
e minuto lado a lado.
<form-date-time asp-for="DataHora" asp-col-css="col-md-5" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-input-css |
string |
— | Classe CSS adicional no input |
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-min |
DateTime? |
— | Data e hora mínimas permitidas |
asp-max |
DateTime? |
— | Data e hora máximas permitidas |
asp-minute-step |
int |
5 |
Intervalo entre os minutos da lista |
asp-on-change |
string |
— | Nome da função JS chamada ao alterar o valor |
asp-show-picker |
bool |
true |
Exibe o botão que abre o painel |
<form-date-time asp-for="Agendamento" asp-min="DateTime.Now" asp-minute-step="30" />
Escolher o dia preenche a hora com 00:00 — ou com o limite, quando 00:00 ficaria fora da faixa —
e o painel só fecha ao escolher o minuto. O asp-on-change recebe "2026-08-15T14:30".
FormSelect
Campo de seleção com busca. Renderiza um painel próprio no lugar do <select> nativo, e o valor
fica em um campo hidden — o model binding e a validação continuam iguais aos dos outros componentes.
<form-select asp-for="CidadeId"
asp-items="Model.Cidades"
asp-value-field="Id"
asp-text-field="Nome"
asp-item-field="Uf"
asp-placeholder="Selecione a cidade"
asp-on-change="onCidadeChange" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-input-css |
string |
— | Classe CSS adicional no campo |
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-items |
IEnumerable |
— | Lista de itens para popular o campo |
asp-on-change |
string |
— | Nome da função JS chamada ao alterar o valor |
asp-text-field |
string |
Text |
Nome da propriedade usada como texto |
asp-value-field |
string |
Value |
Nome da propriedade usada como value |
asp-item-field |
string |
— | Nome da propriedade usada como texto secundário |
asp-placeholder |
string |
Selecionar... |
Texto exibido quando nada está selecionado |
asp-show-search |
bool |
true |
Exibe o campo de busca acima das opções |
asp-search-placeholder |
string |
Buscar... |
Placeholder do campo de busca |
asp-add-href |
string |
— | URL de um botão exibido ao lado do campo |
asp-add-on-click |
string |
— | Nome da função JS chamada no clique do botão |
asp-add-label |
string |
Adicionar |
Texto acessível do botão de atalho |
asp-add-color |
string |
var(--c-primary, #212529) |
Cor da borda e do ícone do botão |
asp-add-icon-color |
string |
#fff |
Cor do ícone do botão sob o ponteiro |
O asp-item-field exibe um segundo texto abaixo do principal, útil para desambiguar itens de mesmo
nome (cidade e UF, produto e código). A busca filtra pelo texto principal.
O asp-add-href renderiza um botão ao lado do campo, para cadastrar um item que ainda não existe:
<form-select asp-for="CidadeId" asp-items="Model.Cidades" asp-add-href="/cidades/novo" />
Com asp-add-href sai um <a>; só com asp-add-on-click, um <button type="button">, que chama a
função sem navegar nem submeter o formulário. Os dois podem ser usados juntos.
<form-select asp-for="CidadeId" asp-items="Model.Cidades" asp-add-on-click="abrirModalCidade" />
O botão é quadrado, com a mesma altura do campo, e segue a mesma regra de cor do Fab: usa
asp-add-color quando informado, senão a variável --c-primary do tema, e por último o neutro
#212529. O asp-add-icon-color é a cor do ícone com o ponteiro sobre o botão.
Com asp-readonly ou asp-disabled o campo não abre. Lista vazia mostra "Nenhuma opção disponível".
Exemplo com asp-on-change
<form-select asp-for="EstadoId"
asp-items="Model.Estados"
asp-value-field="Id"
asp-text-field="Nome"
asp-on-change="onEstadoChange" />
<script>
function onEstadoChange(value) {
// value = id do estado selecionado
console.log('Estado selecionado:', value);
// Exemplo: carregar cidades do estado via AJAX
fetch(`/api/cidades?estadoId=${value}`).
then(r => r.json()).
then(cidades => {
// popular outro select de cidades
});
}
</script>
Selecionando pelo JavaScript
jarvisSelect.select marca um valor no campo, como se a opção tivesse sido clicada: grava o valor
no campo hidden, escreve o texto, fecha o painel e chama o asp-on-change. Devolve false quando o
valor não existe entre as opções.
<form-select asp-for="Uf" asp-items="Model.Estados" />
<form-mask asp-for="Cep" asp-type="CEP" asp-on-complete="onCepComplete" />
<script>
function onCepComplete(value) {
fetch(`https://viacep.com.br/ws/${value.replace(/\D/g, '')}/json/`).
then(r => r.json()).
then(endereco => jarvisSelect.select('Uf', endereco.uf));
}
</script>
Passar '' ou null volta o campo ao placeholder. No FormMultiSelect a função aceita um valor ou
um array, e a lista informada substitui o que estava marcado.
jarvisSelect.select('CidadesIds', ['1', '5']);
O alvo segue as formas de Como identificar o componente.
FormMultiSelect
Campo de seleção múltipla com busca. Cada item vira um checkbox com o mesmo name, então o model
binding preenche a coleção indicada em asp-for.
<form-multi-select asp-for="CidadesIds"
asp-items="Model.Cidades"
asp-value-field="Id"
asp-text-field="Nome"
asp-item-field="Uf"
asp-placeholder="Selecione as cidades" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-input-css |
string |
— | Classe CSS adicional no campo |
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-items |
IEnumerable |
— | Lista de itens para popular o campo |
asp-on-change |
string |
— | Nome da função JS chamada ao marcar/desmarcar |
asp-text-field |
string |
Text |
Nome da propriedade usada como texto |
asp-value-field |
string |
Value |
Nome da propriedade usada como value |
asp-item-field |
string |
— | Nome da propriedade usada como texto secundário |
asp-placeholder |
string |
Selecionar... |
Texto exibido quando nada está selecionado |
asp-show-search |
bool |
true |
Exibe o campo de busca acima das opções |
asp-search-placeholder |
string |
Buscar... |
Placeholder do campo de busca |
asp-show-select-all |
bool |
true |
Exibe a opção que marca e desmarca todos |
asp-select-all-label |
string |
Selecionar todos |
Texto da opção que marca e desmarca todos |
asp-add-href |
string |
— | URL de um botão exibido ao lado do campo |
asp-add-on-click |
string |
— | Nome da função JS chamada no clique do botão |
asp-add-label |
string |
Adicionar |
Texto acessível do botão de atalho |
asp-add-color |
string |
var(--c-primary, #212529) |
Cor da borda e do ícone do botão |
asp-add-icon-color |
string |
#fff |
Cor do ícone do botão sob o ponteiro |
A propriedade do modelo precisa ser uma coleção (List<int>, List<Guid>, etc.). O campo exibe o
texto do item quando há um selecionado e "N selecionados" a partir de dois.
Selecionar todos
A primeira opção do painel marca e desmarca os itens de uma vez, e reflete o estado atual: marcada
com todos selecionados, indeterminada com parte deles. Ela não tem name, então não vai junto no post.
<form-multi-select asp-for="CidadesIds" asp-items="Model.Cidades" asp-select-all-label="Marcar todas" />
<form-multi-select asp-for="CidadesIds" asp-items="Model.Cidades" asp-show-select-all="false" />
Com uma busca em andamento, a opção age apenas sobre os itens visíveis — buscar "SP" e clicar em selecionar todos marca só o que o filtro deixou na tela, preservando o que já estava marcado fora dele.
Trocando as opções via AJAX
Os componentes usam delegação de eventos no document, então campos inseridos depois da carga
(modal, partial via AJAX) já funcionam sem reinicializar nada.
Para trocar as opções de um campo que já está na tela, use jarvisSelect.rebuild, passando o
campo e a lista nova. O jeito mais curto é usar o nome da propriedade do asp-for:
<form-select asp-for="IdConvenio" asp-items="Model.Convenios" asp-on-change="filtrarParticipantes" />
<form-multi-select asp-for="IdsParticipantes" asp-items="Model.Participantes" />
<script>
function filtrarParticipantes(value) {
fetch(`/agenda/agendar?handler=Participantes&idConvenio=${value}`, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }).
then(r => r.json()).
then(items => jarvisSelect.rebuild('IdsParticipantes', items));
}
</script>
Veja Como identificar o componente para as outras formas de apontar o campo.
A lista aceita as chaves em PascalCase ou camelCase, então serializar um GenericList<T> direto do
PageModel funciona:
public IActionResult OnGetParticipantes(Guid? idConvenio)
{
return new JsonResult(_service.ListarParticipantes(idConvenio));
}
| Item | Descrição |
|---|---|
value |
Valor enviado no formulário |
text |
Texto principal |
item |
Texto secundário (opcional) |
O que estava selecionado é preservado quando o item continua existindo na lista nova, e descartado
quando some — no FormSelect o campo volta ao placeholder. O campo de busca, a opção de selecionar
todos e o name dos checkboxes são remontados conforme os atributos do componente, inclusive quando
a lista tinha chegado vazia do servidor.
Quando o rebuild derruba o que estava selecionado, o asp-on-change é chamado com o valor novo
("" no FormSelect, a lista restante no FormMultiSelect), para os campos em cascata reagirem.
Se a seleção sobrevive inteira, nada é disparado.
O asp-add-href renderiza um botão ao lado do campo, para cadastrar um item que ainda não existe:
<form-multi-select asp-for="CidadesIds" asp-items="Model.Cidades" asp-add-href="/cidades/novo" />
<form-multi-select asp-for="CidadesIds" asp-items="Model.Cidades" asp-add-on-click="abrirModalCidade" />
Com asp-add-href sai um <a>; só com asp-add-on-click, um <button type="button">, que chama a
função sem navegar nem submeter o formulário. Os dois podem ser usados juntos.
O asp-on-change recebe a lista dos valores marcados:
<form-multi-select asp-for="CidadesIds" asp-items="Model.Cidades" asp-on-change="onCidadesChange" />
<script>
function onCidadesChange(values) {
// values = ["1", "5", "8"]
console.log('Cidades selecionadas:', values);
}
</script>
Nenhum item marcado não envia nada no post — a coleção chega vazia ou nula, conforme a inicialização da propriedade no modelo.
FormNumeric
Campo numérico com formatação automática (dinheiro, percentual, decimal, inteiro).
<form-numeric asp-for="Valor" asp-type="Money" asp-precision="2" asp-col-css="col-md-4" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-input-css |
string |
— | Classe CSS adicional no input |
asp-precision |
int |
2 |
Casas decimais (limitado a 20) |
asp-type |
NumericType |
Decimal |
Tipo de formatação |
asp-allow-negative |
bool |
false |
Permite negativos; o sinal alterna a cada - digitado |
NumericType
| Valor | Formatação |
|---|---|
Money |
R$ 1.234,56 |
Percent |
12,34% |
Decimal |
1.234,56 |
Integer |
1.234 |
O valor digitado é limitado a 15 dígitos, teto a partir do qual o JavaScript perde precisão numérica.
FormMask
Campo com máscara de entrada (aceita apenas números, formatados automaticamente).
<form-mask asp-for="CPF" asp-type="CPF" asp-col-css="col-md-4" />
<form-mask asp-for="Codigo" asp-type="Custom" asp-mask="##.###-##" />
<form-mask asp-for="Quantidade" asp-type="OnlyNumbers" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-input-css |
string |
— | Classe CSS adicional no input |
asp-type |
MaskType |
CPF |
Tipo de máscara |
asp-on-complete |
string |
— | Nome da função JS chamada ao completar a máscara |
asp-mask |
string |
— | Máscara personalizada (usado com Custom) |
MaskType
| Valor | Máscara | Exemplo |
|---|---|---|
CPF |
###.###.###-## |
123.456.789-00 |
CNPJ |
##.###.###/####-## |
12.345.678/0001-90 |
CPF_CNPJ |
Automático por tamanho | CPF ou CNPJ |
CEP |
#####-### |
12345-678 |
PhoneNumber |
(##) #####-#### |
(11) 99876-5432 |
OnlyNumbers |
Sem máscara, só dígitos | 123456 |
Custom |
Definida por asp-mask |
Conforme o pattern |
Exemplo com asp-on-complete
O callback é disparado quando o usuário termina de preencher todos os dígitos da máscara.
<form-mask asp-for="CPF"
asp-type="CPF"
asp-col-css="col-md-4"
asp-on-complete="onCpfComplete" />
<script>
function onCpfComplete(value) {
// value = "123.456.789-00" (com máscara)
console.log('CPF preenchido:', value);
// Exemplo: buscar dados do cliente pelo CPF
const cpf = value.replace(/\D/g, '');
fetch(`/api/clientes?cpf=${cpf}`).
then(r => r.json()).
then(cliente => {
// preencher campos do formulário
});
}
</script>
<form-mask asp-for="CEP"
asp-type="CEP"
asp-col-css="col-md-3"
asp-on-complete="onCepComplete" />
<script>
function onCepComplete(value) {
// value = "12345-678"
const cep = value.replace(/\D/g, '');
fetch(`https://viacep.com.br/ws/${cep}/json/`).
then(r => r.json()).
then(endereco => {
document.querySelector('[name="Logradouro"]').value = endereco.logradouro;
document.querySelector('[name="Bairro"]').value = endereco.bairro;
document.querySelector('[name="Cidade"]').value = endereco.localidade;
});
}
</script>
Máscara Custom
Use # para representar um dígito numérico. Qualquer outro caractere é inserido automaticamente como separador fixo. O asp-on-complete também funciona com máscaras customizadas.
<form-mask asp-for="Protocolo"
asp-type="Custom"
asp-mask="####/####-##"
asp-on-complete="onProtocoloComplete" />
<script>
function onProtocoloComplete(value) {
// value = "2024/0001-01"
console.log('Protocolo preenchido:', value);
}
</script>
Exemplos de patterns:
##.###-## → 12.345-67
####/#### → 2024/0001
###-## → 123-45
####/####-## → 2024/0001-01
FormDropzone
Área de envio de arquivos: aceita arrastar e soltar ou clicar para escolher, e lista o que foi
selecionado com o tamanho e um botão para remover. O campo é um <input type="file"> comum, então o
model binding continua igual ao dos demais componentes.
<form method="post" enctype="multipart/form-data">
<form-dropzone asp-for="Anexo" asp-col-css="col-md-6" />
<form-dropzone asp-for="Anexos" asp-col-css="col-md-6" asp-multiple="true" />
</form>
[BindProperty] public IFormFile Anexo { get; set; }
[BindProperty] public List<IFormFile> Anexos { get; set; } = [];
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
asp-col-css |
string |
col-md-12 |
Classe CSS da coluna |
asp-input-css |
string |
— | Classe CSS adicional na área |
asp-multiple |
bool |
false |
Permite escolher vários arquivos |
asp-accept |
string |
— | Tipos aceitos, igual ao accept do HTML |
asp-max-size |
int |
— | Tamanho máximo de cada arquivo, em MB |
asp-title |
string |
Arraste o arquivo aqui |
Texto principal da área |
asp-subtitle |
string |
ou clique para escolher |
Texto secundário da área |
asp-remove-label |
string |
Remover |
Texto acessível do botão que remove um arquivo |
asp-color |
string |
var(--c-primary, …) |
Cor de destaque do campo |
asp-on-change |
string |
— | Nome da função JS chamada quando a lista muda |
O formulário precisa de enctype="multipart/form-data". Sem asp-title, o texto acompanha o
asp-multiple (Arraste os arquivos aqui no plural).
O asp-color redefine a cor de destaque dentro do próprio campo, igual ao color do Fab e do
Calendar: vale para o ícone, a borda sob o ponteiro, o destaque durante o arrasto e o ícone de
cada arquivo da lista.
<form-dropzone asp-for="Anexos" asp-multiple="true" asp-color="#e91e63" />
Um ou vários arquivos
Com asp-multiple, os arquivos soltos vão se somando aos que já estavam na lista, sem repetir
nome e tamanho iguais — arrastar em duas levas funciona. Sem ele, o novo arquivo substitui o
anterior, e soltar vários de uma vez guarda só o primeiro.
Escolher pela janela do sistema segue o comportamento nativo: a seleção nova substitui a anterior.
Tipos e tamanho
<form-dropzone asp-for="Imagens" asp-multiple="true" asp-accept="image/*" asp-max-size="1" />
O asp-accept vale tanto no clique (o navegador filtra a janela) quanto no arrastar, onde a
biblioteca confere a extensão e o tipo. Arquivos recusados não entram na lista e aparecem num aviso abaixo da área, separado por motivo:
Tipo não aceito: planilha.xlsx • Acima de 1 MB: foto.png. Quando o sistema não informa o tipo do
arquivo, a extensão decide.
A checagem é do navegador, para poupar o envio: valide também no servidor.
Callback
<form-dropzone asp-for="Anexos" asp-multiple="true" asp-on-change="onArquivos" />
<script>
function onArquivos(files) {
// files = [{ name: "contrato.pdf", size: 3072 }]
document.getElementById('enviar').hidden = files.length === 0;
}
</script>
A função é chamada a cada mudança na lista, seja ao adicionar ou ao remover, e recebe um array com o nome e o tamanho de cada arquivo. Útil para só exibir o botão de envio quando houver algo.
Com asp-readonly ou asp-disabled a área fica apagada, sem clique e sem aceitar arrastar.
Alerts
Componente de alertas Bootstrap que exibe mensagens armazenadas via TempData. Renderiza alertas de info (azul), sucesso (verde), aviso (amarelo) e erro (vermelho).
Na view (layout ou página):
<alerts></alerts>
No PageModel:
// Alerta informativo (azul)
this.Info("Operação realizada.");
// Alerta de sucesso (verde)
this.Success("Registro salvo com sucesso!");
// Alerta de aviso (amarelo)
this.Warning("Atenção: campo opcional não preenchido.");
// Alerta de erro (vermelho)
this.Error("Falha ao salvar o registro.");
return RedirectToPage();
As mensagens são exibidas automaticamente na próxima renderização da página e descartadas após a exibição.
Helpers
is-visible
Controla a visibilidade de qualquer elemento HTML. Quando false, o elemento é completamente removido do output.
<div is-visible="Model.ExibirDetalhes">
Conteúdo visível apenas quando ExibirDetalhes for true.
</div>
<button is-visible="Model.PodeExcluir">Excluir</button>
disabled
Adiciona o atributo disabled a qualquer elemento HTML quando a condição é true.
<button disabled="Model.Processando">Salvar</button>
<input disabled="!Model.PodeEditar" />
Pagination
Navegação entre páginas de uma listagem, no formato ‹ 1 … 4 5 6 … 20 ›. Os links preservam a query
string atual e trocam apenas o parâmetro da página, então filtros e ordenação sobrevivem à navegação.
Com uma página só, nada é renderizado.
<pagination page-number="Model.PageNumber" total-pages="Model.TotalPages" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
page-number |
int |
1 |
Página atual, começando em 1 |
total-pages |
int |
— | Total de páginas |
query-key |
string |
pageNumber |
Nome do parâmetro da página na query string |
sibling-count |
int |
1 |
Páginas exibidas de cada lado da atual |
label |
string |
Paginação |
Texto acessível da navegação |
page-label |
string |
Página {0} |
Texto acessível de cada número ({0} = página) |
previous-label |
string |
Página anterior |
Texto acessível do botão anterior |
next-label |
string |
Próxima página |
Texto acessível do próximo botão |
A primeira e a última página aparecem sempre, e o sibling-count define quantas ficam de cada lado
da atual — o salto entre um número e outro vira reticências. Na página 10 de 20, o padrão mostra
1 … 9 10 11 … 20; com sibling-count="3", 1 … 7 8 9 10 11 12 13 … 20.
A página atual é um <span> destacado na cor primária, sem link. Nas pontas, a seta continua no
lugar, apagada e sem clique, em vez de sumir e fazer a barra pular de posição.
Abaixo de 576px a lista de números não cabe, então ficam só as setas e a página atual — ‹ 6 ›.
Cuidado com
query-key="page": em Razor Pages,pageé reservado pela rota. O model binding tenta converter o caminho da página para número e o handler recebe zero. Por isso o padrão épageNumber.
public void OnGet(int pageNumber)
{
PageNumber = pageNumber > 0 ? pageNumber : 1;
}
Calendar
Calendário mensal com a contagem de itens por dia. Com href, as setas navegam para
{href}?{query-key}=yyyy-MM-dd; sem ele, viram botões que chamam o JavaScript de on-month-change.
Nos dois casos o mês vai como o primeiro dia dele, em formato ISO. O clique em qualquer dia chama o
JavaScript de on-day-select.
<calendar year="Model.Year" month="Model.Month" href="/agenda" days="Model.Days" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
year |
int |
ano atual | Ano exibido |
month |
int |
mês atual | Mês exibido, de 1 a 12 |
days |
IDictionary<int, int> |
— | Quantidade de itens por dia do mês |
color |
string |
var(--c-primary, #212529) |
Cor de destaque do calendário |
href |
string |
— | URL base da navegação entre meses |
query-key |
string |
month |
Nome do parâmetro do mês na query string |
on-month-change |
string |
— | Nome da função JS chamada ao trocar de mês |
on-day-select |
string |
— | Nome da função JS chamada ao escolher um dia |
previous-label |
string |
Mês anterior |
Texto acessível do botão do mês anterior |
next-label |
string |
Próximo mês |
Texto acessível do botão do próximo mês |
picker-label |
string |
Escolher mês e ano |
Texto acessível do cabeçalho |
O days recebe o dia do mês e a quantidade de itens. Os dias presentes no dicionário ganham destaque
e mostram o contador, mas o clique vale para qualquer dia do mês.
public Dictionary<int, int> Days { get; set; } = new() { { 3, 1 }, { 8, 4 }, { 15, 2 } };
O on-day-select recebe a data em formato ISO de qualquer dia clicado, tenha itens ou não, e o
componente marca o dia escolhido:
<calendar year="2026" month="8" href="/agenda" days="Model.Days" on-day-select="mostrarDia" />
<script>
function mostrarDia(date) {
// date = "2026-08-15"
console.log('Dia selecionado:', date);
}
</script>
Os nomes dos dias da semana e do mês vêm da cultura da requisição. O mês fica à esquerda, com o ano abaixo em texto menor, e os botões de navegação à direita. O dia de hoje aparece destacado.
Botões extras no cabeçalho
O conteúdo da tag é renderizado depois das setas, para a página colocar os próprios botões:
<calendar year="Model.Year" month="Model.Month" days="Model.Days" on-month-change="carregarMes">
<button type="button" class="btn btn-sm btn-secondary" onclick="irParaHoje()">Hoje</button>
</calendar>
Trocando o mês sem recarregar
Sem href, as setas viram <button type="button"> e chamam o on-month-change com o primeiro dia
do mês em formato ISO — o mesmo formato do on-day-select. A página busca os dados e troca o
calendário; como o JavaScript usa delegação no document, o calendário novo já funciona sem
reinicializar nada.
<div id="agenda">
<calendar year="Model.Year" month="Model.Month" days="Model.Days" on-month-change="carregarMes" on-day-select="mostrarDia" />
</div>
<script>
function carregarMes(date) {
// date = "2026-07-01"
fetch(`/agenda?handler=Calendario&month=${date}`, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }).
then(r => r.text()).
then(html => document.getElementById('agenda').innerHTML = html);
}
</script>
Com os dois atributos juntos, a seta navega e ainda chama a função.
Trocando o mês por JSON
Em vez do HTML inteiro, o servidor pode devolver só os dados e o jarvisCalendar.rebuild remonta o
cabeçalho e a grade — mesma ideia do jarvisSelect.rebuild. O componente devolvido pelo servidor
continua no lugar: label, cor, callbacks e o seletor de mês seguem valendo.
O modelo é o CalendarModel:
public JsonResult OnGetMes(DateTime month)
{
var days = ObterItensPorDia(month);
return new JsonResult(CalendarModel.Create(month, days));
}
O Create aceita a data do mês ou o par ano e mês, e já preenche o MonthName na cultura da
requisição:
CalendarModel.Create(new DateTime(2026, 5, 1), days);
CalendarModel.Create(2026, 5, days);
CalendarModel.Create(2026, 5);
{ "year": 2026, "month": 5, "monthName": "Maio", "days": { "3": 1, "8": 4, "15": 2 } }
<div id="agenda">
<calendar year="Model.Year" month="Model.Month" days="Model.Days" on-month-change="carregarMes" on-day-select="mostrarDia" />
</div>
<script>
function carregarMes(date) {
fetch(`/agenda?handler=Mes&month=${date}`).
then(r => r.json()).
then(data => jarvisCalendar.rebuild('agenda', data));
}
</script>
| Campo | Obrigatório | Descrição |
|---|---|---|
year |
sim | Ano exibido |
month |
sim | Mês exibido, de 1 a 12 |
monthName |
não | Texto do cabeçalho; sem ele o nome sai da cultura da página |
days |
não | Contagem por dia; sem ele o mês fica sem destaques e sem contador |
As chaves valem em camelCase ou PascalCase, e days aceita tanto o objeto ({ "3": 1 }) quanto
a lista ([{ "day": 3, "count": 1 }]). O alvo segue as formas de
Como identificar o componente — no exemplo, o id da div que
envolve o calendário.
O que o rebuild faz, na ordem:
- Escreve o mês e o ano no cabeçalho.
- Remonta a grade, incluindo as células vazias que fecham a primeira e a última semana.
- Marca o dia de hoje quando ele cai no mês exibido.
- Repõe o destaque e o contador dos dias que vieram em
days. - Reaponta as setas para os meses vizinhos, no
hrefou na chamada doon-month-change. - Atualiza o mês do seletor de mês e ano, e fecha o painel se estava aberto.
- Mantém o dia marcado, se a data continuar existindo no mês novo.
O que não muda: a cor (color), os callbacks, os botões extras do cabeçalho e os textos
acessíveis — tudo isso continua vindo do componente renderizado pelo servidor.
A partir de 768px a grade ganha linhas separando os dias. No mobile as células ficam soltas, com cantos arredondados e sem linhas, para não pesar em tela pequena.
Escolhendo mês e ano
O cabeçalho é um botão: clicar nele abre um painel com os doze meses e setas para trocar de ano.
Escolher um mês segue o mesmo caminho das setas — navega quando há href, ou chama o
on-month-change com a data ISO do primeiro dia.
O painel é montado no cliente, então dá para percorrer vários anos sem recarregar a página. Ele fecha ao escolher um mês, ao clicar num dia ou ao clicar fora, e sempre reabre no ano do mês exibido. Os nomes dos meses vêm da cultura da requisição.
Sem href e sem on-month-change não existe para onde navegar: o cabeçalho fica como texto, sem
setas e sem o seletor.
Cor
O color redefine a variável de destaque dentro do próprio calendário, então vale só para ele:
dias com itens, dia de hoje, dia selecionado, contador e botões de navegação seguem a cor.
<calendar year="2026" month="8" days="Model.Days" color="#e91e63" />
Selecionando pelo JavaScript
jarvisCalendar.select marca um dia e chama o on-day-select, como se ele tivesse sido clicado. Aceita
o número do dia ou a data em formato ISO, e devolve false quando o dia não existe no mês exibido.
<div id="agenda">
<calendar year="2026" month="8" href="/agenda" days="Model.Days" on-day-select="mostrarDia" />
</div>
<script>
jarvisCalendar.select('agenda', 15);
jarvisCalendar.select('agenda', '2026-08-15');
jarvisCalendar.clear('agenda');
</script>
Útil para restaurar o dia escolhido depois de recarregar a página ou trocar de mês. O clear apenas
desmarca, sem chamar o on-day-select.
Sheet
Janela modal montada a partir do próprio formulário: o form vira o painel da janela, e o
componente acrescenta o cabeçalho, o corpo em volta do conteúdo e o rodapé. Abaixo de 576px ela
sobe do rodapé como uma folha, com alça para arrastar e fechar; a partir daí fica centralizada,
como um modal comum.
Este é o único componente da biblioteca que precisa do JavaScript do Bootstrap, que cuida da abertura, do fundo escurecido, do foco e do fechamento pelo Esc. Sem ele a janela não chega a abrir, e nada quebra no resto da página.
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalComentario">Comentar</button>
<form method="post" sheet="modalComentario" sheet-title="Novo comentário" sheet-action="Enviar">
<div class="row g-3">
<form-text-area asp-for="Comentario" asp-placeholder="Escreva seu comentário..." />
</div>
</form>
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
sheet |
string |
— | Id da janela, usado no data-bs-target de quem abre |
sheet-title |
string |
— | Título exibido no cabeçalho |
sheet-action |
string |
— | Texto do botão que envia. Sem ele, não há rodapé |
sheet-cancel |
string |
Cancelar |
Texto do botão que fecha sem enviar |
sheet-style |
string |
primary |
Variação do botão de envio, no padrão Bootstrap |
O form continua sendo um formulário comum: method, action, asp-page-handler e os campos
dentro dele funcionam como em qualquer outro lugar. Sem sheet-action a janela sai sem rodapé,
para conteúdo apenas de leitura.
No celular, arrastar o cabeçalho para baixo mais de 110px fecha a janela; menos que isso ela volta para o lugar. O arrasto é ignorado no desktop.
Fab
Botão de ação flutuante fixo no canto inferior direito, visível apenas no mobile — fica oculto a partir de 768px. Renderiza um link circular com ícone Font Awesome.
<fab href="/clientes/novo" icon="fa-solid fa-plus" label="Novo cliente" />
<fab href="/pedidos/novo" icon="bi bi-file-earmark-text" color="#e91e63" icon-color="#fff" label="Novo pedido" />
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
href |
string |
— | URL de destino ao clicar |
on-click |
string |
— | Nome da função JS chamada no clique |
icon |
string |
+ em SVG |
Classe do ícone, renderizada em um <i> |
color |
string |
var(--c-primary, #212529) |
Cor de fundo do botão |
icon-color |
string |
#fff |
Cor do ícone |
label |
string |
— | Texto acessível do botão (aria-label) |
Com href sai um <a>; sem ele, um <button type="button">, para o clique apenas chamar a função
sem navegar nem submeter o formulário em volta. Os dois podem ser usados juntos.
<fab on-click="abrirFiltro" icon="fa-solid fa-filter" label="Filtrar" />
O icon é repassado como veio, sem prefixo: serve para Font Awesome, Bootstrap Icons, Material Icons ou qualquer outra biblioteca. A biblioteca escolhida precisa estar carregada na página. Sem icon e sem conteúdo na tag, o botão usa um "+" desenhado em SVG, que não depende de biblioteca alguma.
Para um ícone que não venha de fonte, informe o conteúdo dentro da própria tag — ele substitui o <i>:
<fab href="/carrinho" label="Carrinho">
<svg viewBox="0 0 24 24"><path d="..." /></svg>
</fab>
svg e img dentro do botão são dimensionados em 1.5em (30px) e herdam a cor do ícone via fill: currentColor.
A cor pode vir do atributo color ou do tema do app, pela variável CSS --c-primary. Sem nenhum dos dois, o fallback é #212529.
ButtonWhatsApp
Botão flutuante de WhatsApp com animação de pulso. Renderiza um ícone fixo no canto inferior direito que abre uma conversa no WhatsApp.
<button-whats-app number="5511999999999" text="Olá, preciso de ajuda!" />
| Propriedade | Tipo | Descrição |
|---|---|---|
number |
string |
Número do WhatsApp com código do país (sem +, espaços ou traços) |
text |
string |
Texto pré-preenchido na mensagem |
Requer o CSS da biblioteca. O ícone é um PNG embutido em base64 e a animação de pulso usa o keyframe jarvis-whatsapp-pulse.
Extensions
Métodos de extensão disponíveis para uso no PageModel e HttpContext.
Validação
if (ModelState.NotValid())
{
return Page();
}
Listas para Select
Converte uma GenericList<T> do Jarvis.Toolkit em IEnumerable<SelectListItem>, pronta para o asp-items do FormSelect.
Estados = _service.ListarEstados().ToSelectListItem();
<form-select asp-for="IdEstado" asp-items="Model.Estados" />
IP e Dispositivo do Cliente
var ip = HttpContext.GetIpAddress();
var device = HttpContext.GetDevice();
GetIpAddress normaliza endereços IPv4 recebidos em formato mapeado (::ffff:189.10.20.30) para IPv4 puro. Endereços IPv6 reais são mantidos intactos, então a coluna de IP no banco precisa comportar 45 caracteres.
GetDevice retorna o cabeçalho User-Agent já com Trim(). Devolve string vazia quando o cabeçalho não é enviado.
Atrás de Proxy Reverso
Com IIS/ARR, Docker Swarm ou nginx na frente, GetIpAddress devolve o IP do proxy, não o do cliente. Para obter o IP real, registre o processamento dos cabeçalhos encaminhados:
builder.Services.AddJarvisIpCliente();
var app = builder.Build();
app.UseJarvisIpCliente(); // primeira linha do pipeline, antes de auth e HTTPS redirect
Isso configura X-Forwarded-For e X-Forwarded-Proto com ForwardLimit = 1 (um único proxy na frente). Sem parâmetro, aceita cabeçalhos vindos das faixas privadas 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 e loopback.
Informando os proxies explicitamente, somente esses endereços são aceitos:
builder.Services.AddJarvisIpCliente("192.168.1.50");
Segurança: confiar em faixas inteiras permite que qualquer host daquela rede forje o cabeçalho
X-Forwarded-Fore se passe por outro IP — num cluster Swarm, isso inclui qualquer container. Se o IP for usado para bloqueio, rate limit ou auditoria, passe o endereço do proxy explicitamente.
Notas de infraestrutura:
- O IIS/ARR envia
X-Forwarded-Forpor padrão, mas não oX-Forwarded-Proto— este exige uma regra de rewrite definindo a server variableHTTP_X_FORWARDED_PROTO. Sem ela,UseHttpsRedirectionpode entrar em loop. - No Docker Swarm, o ingress faz SNAT e altera apenas o IP de origem (L3); o cabeçalho
X-Forwarded-Forpassa intacto. Como o gateway do ingress fica em10.0.0.0/8, o modo padrão de publicação funciona. Publicar commode: hostsó é necessário para obter o IP real sem depender de cabeçalho. - Se um dia entrar outro proxy na frente (Cloudflare, nginx), o
ForwardLimitprecisa subir para 2.
Rate Limit
Controle de requisições por política nomeada, particionado por IP do cliente e caminho da requisição.
builder.Services.AddJarvisRateLimit(options =>
{
options.Message = "Muitas tentativas em pouco tempo. Aguarde alguns minutos e tente de novo.";
options.AddPolicy("auth", 5, TimeSpan.FromMinutes(1), HttpMethods.Post);
});
builder.Services.AddRazorPages(options =>
{
options.Conventions.AddJarvisRateLimit("/Login", "auth");
options.Conventions.AddJarvisRateLimit("/RecuperarSenha", "auth");
});
var app = builder.Build();
app.UseJarvisRateLimit(); // depois de UseRouting, antes de MapRazorPages
Aplicando a política a uma pasta inteira:
options.Conventions.AddJarvisRateLimitFolder("/Conta", "auth");
Ou direto no PageModel, sem convenção:
using Microsoft.AspNetCore.RateLimiting;
[EnableRateLimiting("auth")]
public class LoginModel : PageModel
{
public void OnGet() { }
public async Task<IActionResult> OnPostAsync() { }
}
O atributo vale para a página inteira, não por handler — a política é que decide quais métodos HTTP conta. Para liberar uma página dentro de uma pasta limitada, use [DisableRateLimiting].
Com uma política só, dá para registrar direto:
builder.Services.AddJarvisRateLimit("auth", 5, TimeSpan.FromMinutes(1), HttpMethods.Post);
Limitar somente
HttpMethods.Posté o normal em Razor Pages: oGETcontinua exibindo a página e apenas o envio do formulário conta para o limite.
Opções
| Propriedade | Padrão | Descrição |
|---|---|---|
Message |
"Muitas tentativas…" | Mensagem exibida no alerta de erro |
StatusCode |
429 |
Status code da resposta bloqueada |
RedirectOnRejected |
true |
Volta para a página de origem com a mensagem no alerta |
OnRejected |
null |
Substitui a resposta padrão; quando definido, as opções acima deixam de ser aplicadas |
Policies |
vazio | Políticas registradas |
Política
| Propriedade | Padrão | Descrição |
|---|---|---|
Name |
— | Nome usado ao aplicar a política à página |
PermitLimit |
5 |
Requisições permitidas por janela |
Window |
1 min |
Duração da janela |
QueueLimit |
0 |
Requisições que aguardam na fila em vez de serem rejeitadas |
SegmentsPerWindow |
0 |
Acima de 1, troca janela fixa por janela deslizante |
Methods |
null |
Métodos HTTP limitados. Nulo ou vazio limita todos |
IncludePath |
true |
Inclui o caminho na chave — cada página tem sua própria contagem |
IncludeUser |
false |
Inclui o usuário autenticado na chave; anônimos continuam contados por IP |
Configuração detalhada:
builder.Services.AddJarvisRateLimit(options =>
{
options.AddPolicy("conta", policy =>
{
policy.PermitLimit = 20;
policy.Window = TimeSpan.FromMinutes(5);
policy.SegmentsPerWindow = 5; // janela deslizante de minuto em minuto
policy.Methods = [HttpMethods.Post];
policy.IncludeUser = true;
});
});
Resposta bloqueada
- Requisição normal: a mensagem vai para o alerta de erro (TempData) e o usuário volta para a página de origem, onde o componente
<alerts>a exibe. - Requisição AJAX (
X-Requested-With: XMLHttpRequestouAccept: application/json): JSON no formatoModelResponse. - Sem
Referer, emGET, ou comRedirectOnRejected = false: status429com a mensagem em texto puro.
Em todos os casos o cabeçalho Retry-After é enviado, em segundos.
O redirecionamento devolve
302, não429— o status de bloqueio se perde no caminho. Se o cliente depende do429(monitoramento, integração), useRedirectOnRejected = falseou umOnRejectedpróprio.
Contagem em memória: o limite vale por instância da aplicação. Com várias réplicas (Docker Swarm, web farm), o limite efetivo é multiplicado pelo número de réplicas. Para limite compartilhado, use um limitador distribuído (Redis).
A chave de partição usa
GetIpAddress(). Atrás de proxy reverso,UseJarvisIpCliente()precisa vir antes no pipeline — sem isso todas as requisições caem na mesma partição, a do IP do proxy.
Autenticação
// Login
await HttpContext.LoginAsync(user);
// Logout
await HttpContext.LogoutAsync();
// Atualizar uma claim
await HttpContext.UpdateClaimAsync("Role", "Admin");
// Atualizar múltiplas claims
await HttpContext.UpdateClaimsAsync(
new JarvisClaim("Role", "Admin"),
new JarvisClaim("Name", "João")
);
// Reemitir o cookie a partir de uma identidade já montada
await HttpContext.RefreshLoginAsync(identity);
| Método | Descrição |
|---|---|
LoginAsync |
Cria a identidade a partir de JarvisAuthenticationUser e autentica |
LogoutAsync |
Encerra a sessão de autenticação |
RefreshLoginAsync |
Reemite o cookie a partir de um ClaimsIdentity existente |
UpdateClaimAsync |
Substitui uma claim e reemite o cookie |
UpdateClaimsAsync |
Substitui várias claims e reemite o cookie |
UpdateClaimAsync e UpdateClaimsAsync reemitem o cookie de autenticação — o usuário permanece logado, mas HttpContext.User na requisição atual continua com os valores antigos. A mudança só aparece na próxima requisição.
Todos emitem o cookie com
IsPersistent = true, ou seja, a sessão sobrevive ao fechamento do navegador. Não há opção de sessão temporária.
Sessão
// Armazenar string
HttpContext.SetSession("Token", "abc123");
// Armazenar objeto (serializado automaticamente)
HttpContext.SetSession("Carrinho", carrinho);
// Recuperar string
var token = HttpContext.GetSession("Token");
// Recuperar objeto (deserializado automaticamente)
var carrinho = HttpContext.GetSession<Carrinho>("Carrinho");
// Remover
HttpContext.DeleteSession("Token");
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Jarvis.Toolkit (>= 1.2.2.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.1 | 0 | 9/17/2026 |
| 2.0.0.9 | 76 | 9/13/2026 |
| 2.0.0.8 | 108 | 8/19/2026 |
| 2.0.0.7 | 100 | 8/19/2026 |
| 2.0.0.6 | 104 | 8/17/2026 |
| 2.0.0.5 | 107 | 8/16/2026 |
| 2.0.0.4 | 104 | 8/16/2026 |
| 2.0.0.3 | 100 | 8/15/2026 |
| 2.0.0.2 | 96 | 8/10/2026 |
| 2.0.0.1 | 111 | 8/2/2026 |
| 2.0.0 | 112 | 8/1/2026 |
| 1.0.1.6 | 117 | 5/19/2026 |
| 1.0.1.5 | 140 | 2/1/2026 |
| 1.0.1.4 | 126 | 1/28/2026 |
| 1.0.1.3 | 223 | 11/28/2025 |
| 1.0.1.2 | 328 | 11/13/2025 |
| 1.0.1.1 | 233 | 10/1/2025 |
| 1.0.1 | 167 | 7/11/2025 |
| 1.0.0.9 | 348 | 3/26/2025 |
| 1.0.0.8 | 188 | 2/13/2025 |