*.cshtml page
#{
var companyLoginFormViewModel = TempData["CompanyLoginFormViewModel"] as CompanyLoginFormViewModel;
}
<form class="login-form" action="#Url.Action("Login")" name="companyLoginForm" method="post">
<input type="hidden" name="rememberMe" value="#companyLoginFormViewModel.LoginViewModel.RememberMe" />
<button type="submit" class="btn btn-success uppercase">#L("LogIn")</button>
</form>
Note : This value (#companyLoginFormViewModel.LoginViewModel.RememberMe) is always true or false
VMs
public class CompanyLoginFormViewModel
{
public LoginViewModel LoginViewModel { get; set; }
}
public class LoginViewModel
{
public bool RememberMe { get; set; }
}
Action Method :
[HttpPost]
public virtual async Task<JsonResult> Login(LoginViewModel loginModel)
{
if (!ModelState.IsValid) //here it shows validation error
{
throw new UserFriendlyException(L("FormIsNotValidMessage"));
}
}
Question : When I pass a true or false through hiden field,it always says The RememberMe field is required. validation error on the server side.But that property is not a required field ? Could you tell me why this is happening ? Thanks.
You can add class="ignore" in you hidden field, and put this in your javascript code to ignore in client side:
$.validator.setDefaults({
ignore: ".ignore"
});
server side:
#Html.Hiddenfor(model => model.rememberMe,
new Dictionary<string, object> { { "data-val", false }})
you are using MVC ,Please use the facilities it:
#model CompanyLoginFormViewModel
#{
}
#using (Html.BeginForm("Your Action","your controller",FormMethod.Post))
{
#Html.Hiddenfor(model => model.rememberMe,
new Dictionary<string, object> { { "data-val", false } })
<button type="submit" class="btn btn-success uppercase">#L("LogIn")</button>
}
Related
I have an razor page receiving an PageModel descendant.
For exibition-only pages (dashboards, reports, etc), it works like a charm. I use the base PageModel class to make available to turn all pages reflect my hosting.json configuration, making links inside the app dynamic.
So the hierarchy is: PageModel --> BasePageModel --> ConfigTransmissorFragModel
Now I have this SendingConfiguration model (I will process it in lower level layer):
public class SendingConfiguration
{
// I will edit this one
[DisplayName("Send it to production server?")]
public bool ProductionServer { get; set; }
}
The PageModel is as below:
// BasePageModel will process the urls of the site.
public class ConfigTransmissorFragModel : BasePageModel
{
public SendingConfiguration SendConfig { get; set; }
private void UpdateSendingConfiguration()
{
var Config = InfoExibicao.ConfigAtiva;
ConfigEnvio.ProductionServer = Config.ProductionServer;
}
private InfoConfiguration _InfoExibition;
public InfoConfiguration InfoExibition
{
get { return _InfoExibition; }
set
{
_InfoExibition = value;
}
}
public ConfigTransmissorFragModel() : base(null)
{
SendConfig = new SendingConfiguration();
}
public ConfigTransmissorFragModel(
ConfigDashboard PConfigDash,
InfoConfiguration PInfoConfig
) : base(PConfigDash)
{
ConfigEnvio = new SendingConfiguration();
this.InfoExibition = PInfoConfig;
UpdateSendingConfiguration();
}
}
The Model is generated in controller:
var Fch = FacadeInfoConfig;
var InfoConfig = Fch.ObterInfoConfiguracao(IdTransmissor);
var CfgDash = ConfigDash;
var Modelo = new ConfigTransmissorFragModel(CfgDash, InfoConfig);
Modelo.UrlServer = ConfigHost.WebServiceUrl;
In the razor page I have this header:
#page
#using ESenderWebService.ModeloPagina
#using Microsoft.AspNetCore.Mvc.RazorPages;
#using Negocio.Integracao.Configuracao;
#model ESenderWebService.ModeloPagina.ConfigTransmissorFragModel
#{
//ViewData["Title"] = "Configuracao de Transmissor para envio";
Layout = "~/Pages/Shared/_Layout_Dashboard.cshtml";
}
The form:
<form autocomplete="off" asp-controller="InfoConfig" asp-action="SalvarConfig" method="post">
<div class="form-group">
<fieldset>
<legend style="width: auto; margin-bottom: auto;"> Configurações </legend>
<div>
#Html.CheckBoxFor(m => m.SendConfig.ProductionServer)
#Html.LabelFor(m => m.SendConfig.ProductionServer)
</div>
<div>
<button class="btn btn-primary " name="submit" type="submit">Save</button>
</div>
</fieldset>
</div>
</form>
My problem is: how to receive SendingConfiguration on my post handler?
// This one explodes complaining that HttpContext is missing
[HttpPost()]
public IActionResult SalvarConfig(ConfigTransmissorFragModel PModel)
// This one never reflects what I mark in the form
// It always returns false in PModel.ProductionServer
[HttpPost()]
public IActionResult SalvarConfig([FromForm] SendingConfiguration PModel)
What I'm doing wrong?
I am not sure why the first HttpPost syntax exploding in your case, because its working fine in my sample project.
Well as said the description, when I pass data from the get of my method to the view everything its fine, but, when i hit save button and it goes to the server to validate if model is valid, if certanly its valid everyting its ok, but, if the model is not valid he go back to the same view (as is suppouses to do), but it lost the collection when it goes to the server, I consider is so stupid fill it again, so, Theres any way to fill the model only one tyme for the select list?.
I have those actions
public IActionResult Create()
{
var types = _context.Types;
var vm = new EventViewModel
{
Types = new SelectList(types, "Id", "Name")
};
return View(vm);
}
[Authorize]
[HttpPost]
public IActionResult Create(EventViewModel vm)
{
//when it enter here **Types** comes empty
//if is not valid
if (!ModelState.IsValid)
{
//var types = _context.Types;
//**I WANT TO AVOID THIS**
//vm.Types = new SelectList(types, "Id", "Name", vm.TypeId);
return View(vm);
}
var ev = new Event
{
DateTime = vm.GetDateTime(),
TypeId = vm.TypeId,
Venue = vm.Venue,
CoachId = User.FindFirstValue(ClaimTypes.NameIdentifier)
};
_context.Add(ev);
_context.SaveChanges();
return RedirectToAction("Index", "Home");
}
This is my view
#model EcCoach.Web.ViewModels.EventViewModel
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<form asp-action="Create">
<p class="alert alert-info">
All fields are <strong>required</strong>
</p>
<div class="form-group">
<label asp-for="Venue"> </label>
<input asp-for="Venue" class="form-control" placeholder="Enter a
venue" autofocus="autofocus">
<span asp-validation-for="Venue"></span>
</div>
<div class="form-group">
<label asp-for="Date"> </label>
<input asp-for="Date" class="form-control" placeholder="eg
15/12/2017">
</div>
<div class="form-group">
<label asp-for="Time"> </label>
<input asp-for="Time" class="form-control">
</div>
<div class="form-group">
<label asp-for="TypeId"> </label>
<select asp-for="TypeId" asp-items="#Model.Types" class="form-
control">
<option value="0">Choose One</option>
</select>
</div>
<button type="submit" class="btn btn-primary">
Save
</button>
</form>
And finally this is my ViewModel
public class EventViewModel
{
[Required]
[FutureDate]
public string Date { get; set; }
[Required]
[ValidTime]
public string Time { get; set; }
[Required]
public byte TypeId { get; set; }
[Required]
[StringLength(5)]
public string Venue { get; set; }
public DateTime GetDateTime()
{
return DateTime.Parse($"{Date} {Time}");
}
public IEnumerable<SelectListItem> Types { get; set; }
}
You have few ways to do it.
The worst way is to store this values on the UI in the hidden inputs in the form, so the will be added to the POST request and you will recive them as a part of the VM in your post method. It's bad because everyone can change them, and it can be source of vulnerability. You should never trust users input.
The second way is to store the original VM in the session. If you are not required to be statless - it's the best solution. So, in the GET action you should store the VM in the session before returning to the client. In the post request you will recive the requestVM as the method paramter ( from the post). It will contains only user input. Then, you should get the sessionVM from the session, map user input from requestVM to sessionVM. Finaly, in the sessionVM you will have all values that you had on GET + updated fields from user input. Then you should validate the sessionVM and use the values from it to update the DB. If the validation fails, you should return the view with the sessionVM as a model.
Try to use IMemoryCache to save the list of type into cache.
1.In startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
2.ApplicationDbContext:
public DbSet<Type> Types {get;set;}
3.Controller:
public class MyController : Controller
{
private readonly ApplicationDbContext _context;
private IMemoryCache _cache;
public MyController(ApplicationDbContext context, IMemoryCache memoryCache)
{
_context = context;
_cache = memoryCache;
}
public IActionResult Create()
{
var types = _context.Types.AsNoTracking().ToList();
var vm = new EventViewModel
{
Types = new SelectList(types, "Id", "Name")
};
_cache.Set("types",types);//cache data
return View(vm);
}
[HttpPost]
public IActionResult Create(EventViewModel vm)
{
if (!ModelState.IsValid)
{
var types = _cache.Get<List<Type>>("types");//get cached data
vm.Types = new SelectList(types, "Id", "Name", vm.TypeId);
return View(vm);
}
return RedirectToAction("Index", "Home");
}
}
This case can also be seen in the documentation of Microsoft.
Documentation
A part of the example:
if (await TryUpdateModelAsync<Instructor>(
newInstructor,
"Instructor",
i => i.FirstMidName, i => i.LastName,
i => i.HireDate, i => i.OfficeAssignment))
{
_context.Instructors.Add(newInstructor);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
PopulateAssignedCourseData(_context, newInstructor);
return Page();
If the validation fails (or something else), the method PopulateAssignedCourseData(_context, newInstructor); that recreates the data is called again.
I am creating a web app in asp.net mvc-5,
I am using IValidatableObject interface for validations,
here is how my model looks,
public class LicenseInfo : IValidatableObject
{
public int LicenseId { get; set; }
//other properties
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//Validate class which will be called on submit
}
}
My view
#using (Ajax.BeginForm("_AddEditLicense", "User", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "dvLicenseContent", OnSuccess = "fnAddEditOnSuccess" }))
{
#Html.ValidationSummary(false)
#Html.DropDownListFor(m => m.LicenseId, new SelectList(Model.LicenseData, "Value", "Text"), "Select....", new { #class = "form-control" })
#*other html elements*#
<input type="submit" value="#ViewBag.Submit" id="btnaddLicense" class="btn btn-primary btn-block" />
}
My Controller
[HttpPost]
public ActionResult _AddEditLicense(LicenseInfo data)
{
if (ModelState.IsValid)
{
//execution
}
}
when my LicenseId = 0 then my validation is not working and the debugger on my controller is executing directly, but when LicenseId > 0 then my validation method is executing.
You need to manually add the validation inside your controller method.
[HttpPost]
public ActionResult _AddEditLicense(LicenseInfo data)
{
if (ModelState.IsValid)
{
// Execute code
}
// Not validated, return to the view
return View(data);
}
EDIT
Well, 0 is a valid value for an int even if does not represent anything in your drop down.
Try to change it to int?, then the default value would be null and it should be easier to catch it in model validation.
I have the following view:
#model dynamic
<form class="form-horizontal" id="formDynamicItem" action="/DynamicItem/SaveItem" method="post">
#Html.AntiForgeryToken()
<div class="col-xs-12 buttonBar">
<button type="submit" value="Save" name="submitButton" class="btn">Save</button>
<button type="submit" value="Cancel" name="submitButton" class="btn">Cancel</button>
</div>
<div class="col-lg-6">
<div class="ibox ">
<div class="ibox-content">
#{
foreach (var obj in Model)
{
var kvpObj = (KeyValuePair<string, object>)obj;
var entityProp = (EntityAttributeProperties)kvpObj.Value;
<div class="form-group">
#if (entityProp.IsHiddenField)
{
<input type="hidden" class="form-control" data-val="true" id="#kvpObj.Key" name="#kvpObj.Key" value="#entityProp.Value" />
}
else if (entityProp.IsFormField)
{
var isReadOnly = entityProp.IsReadonly ? "readonly" : "";
IHtmlString validationRules = Html.Raw(string.Empty);
if (entityProp.ValidationRules != null)
{
validationRules = entityProp.ValidationRules;
}
#Html.Label(entityProp.Name, new { #class = labelClass })
<div class="#controlClass">
#switch (#entityProp.Type)
{
//... many cases
default:
<input type="text" class="form-control" id="#kvpObj.Key" name="#kvpObj.Key" value="#entityProp.Value" #isReadOnly #validationRules />
break;
}
</div>
}
</div>
}
}
</div>
</div>
</div>
</form>
#section Scripts {
<script>
$("#formDynamicItem").validate();
</script>
}
And in the controller I get my values using FormCollection:
public ActionResult SaveItem(FormCollection form)
{
...
newValue = typeConverter.ConvertFromString(form[entityAttribute.Name]);
...
}
}
My question is the following:
How can I establish Server-side validation on such dynamic model? Can I use FormCollection somehow? Possibly build dynamic view model somehow? If anyone has experience in this please consider giving a suggestion(answer).
Update: Making Detail page with ViewModel insted of dynamic model
So, After much refactoring I appear to be again stuck with server-side validation:
So now I have this ViewModel:
public class DynamicItemViewModel
{
public Guid Id { get; set; }
public List<EntityAttributeProperties> Properties { get; set; }
}
this detail page:
#model ExactDistillation.Models.DynamicItem.DynamicItemViewModel
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
#using (Html.BeginForm("SaveItem", "DynamicItem", FormMethod.Post, new { #class = "form-horizontal", #id = "formDynamicItem" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(x => x.Id)
<div class="col-xs-12 buttonBar">
<button type="submit" value="Save" name="submitButton" class="btn btn-primary pull-right">Save</button>
<button type="submit" value="Cancel" name="submitButton" class="btn btn-default pull-right cancel">Cancel</button>
</div>
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title text-webwonders">
<h5>Item</h5>
</div>
<div class="ibox-content">
#{
for (int i = 0; i < Model.Properties.Count; i++)
{
#Html.EditorFor(m => Model.Properties[i], "EntityAttributeProperties", "Properties[" + i + "]")
}
}
</div>
</div>
</div>
}
</div>
</div>
And this is how I define EntityAttributeProperties page:
#model EntityAttributeProperties
<div class="form-group">
#if (Model.IsHiddenField)
{
#Html.HiddenFor(x => x.Value)
}
else if (Model.IsFormField)
{
#Html.Label(Model.Name, new { #class = "col-sm-5 col-md-4 col-lg-3" })
<div class="col-sm-7 col-md-8 col-lg-9">
#switch (Model.Type)
{
--- Many cases
default:
#Html.DynamicTextBoxFor(m => m.Value, null, Model.IsReadonly, Model.ValidationRules)
break;
}
</div>
}
</div>
EntityAttributesProperties looks the following way:
public class EntityAttributeProperties
{
public string Name { get; set; }
public string DisplayName { get; set; }
public object Value { get; set; }
public EntityAttributeDataTypeEnum Type { get; set; }
public short Order { get; set; }
public bool IsFormField { get; set; }
public bool IsReadonly { get; set; }
public bool IsHiddenField { get; set; }
public Dictionary<string,object> ValidationRules { get; set; }
}
So, I am trying to make server-side validation for Model, but I am stuck, because I don't find any elegant solution to my problem, just solutions in which I have to do a lot of hardcoding (which I don't like).
Here is how I receive the form Submit:
public ActionResult SaveItem(DynamicItemViewModel model)
{
List<EntityAttributeExtendedView> entityAttributes = GetItemEntityAttributes();
DataObject dataObject = _dbContext.DataObjectCollection.FirstOrDefault(x => x.Id == model.Id);
if (dataObject != null)
{
JObject json = JObject.Parse(dataObject.Value);
dynamic dynJson = JsonConvert.DeserializeObject(dataObject.Value);
// Possibly loop through all entity attributes and separately make a custom validation ?
// Or somehow create custom Model State for validation ?
}
return View("Detail", model);
}
I will appreciate any suggestion on how to approach the problem with server side validation.
Thanks.
FormCollection is a very raw form of data and cannot be validated easily. I'd suggest you should refactor your view to be able to use ViewModels, else you will have a hard time working with the Data.
I cannot show you the full way but give you some hints:
Create a single View Model class for the View which contains a list of Items (of type EntityAttributeProperties). Let's call it MainViewModel.
Pass this View Model to the view rather then the Dictionary
In your view, use #Html.EditoFor(x => x.Items) to generate the correct HTML. ASP.NET MVC will use the Editor Templates for EntityAttributeProperties type
This is a good moment to create a new view EntityAttributeProperties.cshtml in your View folders EditorTemplates sub-folder
within this item view, you can do all your entityProp.Type switches, but be careful with ID generation, always use #Html.IdFor(...) etc. instead of generating own IDs to keep type safe with your View Model
After some tweaks, your Post Action should be able to receive your view model of Type MainViewModel. If everything went good, the Item's will be filled, even if you used different Controls (hidden fields, text fields, drop-downs...) to populate the Values
From my perspective, only this MVC-safe approach will lead to success in this case
I'm adding another Answer because the Question changed heavily.
A good approach is to use the IValidatableObject Interface. So you add this Interface to your EntityAttributeProperties class and have to override the Method Validate. for simple validation like required fields, you use so called validation Attributes.
Your EntityAttributeProperties class would be decorated like this:
public class EntityAttributeProperties : IValidatableObject
{
public string Name { get; set; }
[Required]
public object Value { get; set; }
...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
if (... /* some condition, e.g. specific EntityAttributeDataTypeEnum */)
{
// Do some validation
// some other random test
if (.../* something not right... */)
{
results.Add(new ValidationResult("your input was not valid!"));
}
}
return results;
}
}
You might need to also make your DynamicItemViewModel and IValidatableObject and loop through the Items, but sometimes MVC is smart enough to validate sub-items automatically, so you might need this:
public class DynamicItemViewModel : IValidatableObject
{
...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return Items.SelectMany(x => x.Validate(validationContext));
}
}
OK now in your Controller, you basically need to check your ModelState. The automatically generated ModelState Property contains all errors.
Is there anyway to make the imagination? Like this:
#using (Html.BeginForm("action1", "controller1"))
{
#Html.TextBoxFor(m => m.MyTextBox)
}
#using (Html.BeginForm("action2", "controller1"))
{
<input type="submit" value="Click me" />
}
My model:
public class MyModel
{
public string MyTextBox { get; set; }
}
and Action2:
public ActionResult Action2(MyModel m)
{
ViewBag.Value = m.MyTextBox;
return View();
}
Can I do that?
You would need to use client script to tie the fields together as the user performs the update - nothing server or view side. The example is using JQuery, but you could whatever flavor you prefer.
#using (Html.BeginForm("action1", "controller1"))
{
#Html.TextBoxFor(m => m.MyTextBox, new { data_sync_target = ".MyTextBox-dest" })
}
#using (Html.BeginForm("action2", "controller1"))
{
#Html.HiddenFor(m => m.MyTextBox, new { #class = "MyTextBox-dest" })
<input type="submit" value="Click me" />
}
<script language="javascript">
$(document).ready(function() {
$('input[data-sync-target]').keyup(function() {
var $target = $($(this).data('sync-target'));
$target.val($(this).val());
});
});
</script>