Blazor validation over a MongoDB Datamodel - mongodb

I try to link my MongoDB class model to my Blazor page component. I tried to keep all the System.ComponentModel.DataAnnotations.ValidationAttribute to an interface and let the 'real' class with the Bson decoration apart as:
public interface ITestIt
{
int id { get; set; }
[Required(ErrorMessage = "Material cost is required")]
[StringLength(5, ErrorMessage = "Name is too long.")]
string MyName { get; set; }
}
public class TestIt : ITestIt
{
[BsonId]
public int id { get; set; }
public string MyName { get; set; }
}
And include it in my page as:
<h1>Hello, world!</h1>
Welcome to your new app.
<EditForm Model=#testIt OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group">
<label for="Name">Name</label>
<InputText #bind-Value=testIt.MyName class="form-control" id="Name" />
<ValidationMessage For="() => testIt.MyName" Description="Salut" />
</div>
<input type="submit" class="btn btn-primary" value="Save" />
</EditForm>
#code{
private ITestIt testIt;
private bool IsDone = false;
protected override async Task OnInitializedAsync()
{
if (IsDone) return;
testIt = new TestIt();
IsDone = true;
}
private void HandleValidSubmit()
{
Console.WriteLine("OnValidSubmit");
}
But it don't work, what is the best way to separate the both without having to decorate all my data model with the DataAnnotations tags and not having to copy one by one each property one by one to and other object?
Thanks!

I don't think Blazor reflects over data annotations of interfaces, only the properties of the implementing object.
I keep my validations in a separate project completely. To do this I use FluentValidation.
You can write a component that accepts an EditContext as a cascading parameter, hook into the events where it requests validation, and execute the FluentValidation code.
Or you can use a pre-made library such as https://www.nuget.org/packages/PeterLeslieMorris.Blazor.FluentValidation/

Related

ICollection property is empty during OnValidSubmit()

I am trying the free community version of Syncfusion Blazor SfGrid for my basic CRUD of a ICollection property. But while I am able to add, edit, delete at runtime (data is perfectly shown in the datagrid), my ICollection property is always empty during OnValidSubmit()...
1st: My data models look like this:
public class Contact
{
public int ID { get; set; }
public string AccountName { get; set; }
public ICollection<ContactPerson> ContactPersons { get; set; }
}
public class ContactPerson
{
public int ID { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
}
2nd: My create.razor uses my ContactForm.razor for reusability
<h1>Create</h1>
<hr />
<div style="margin: 50px">
<ContactForm Contact="contact" OnValidSubmit="#CreateRecord" OnCancelButton="#CancelRecord"/>
</div>
#code {
private Contact contact = new Contact
{
ContactPersons = new ContactPerson[0],
ContactAddresses = new ContactAddress[0]
};
void CancelRecord()
{
uriHelper.NavigateTo("contact");
}
async Task CreateRecord()
{
await http.PostAsJsonAsync("api/Contact", contact);
uriHelper.NavigateTo("contact");
}
}
3rd: My ContactForm.razor looks like this (which I use for creating and editing a record):
#if (Contact == null)
{
<text>Loading...</text>
}
else
{
<EditForm Model="#Contact" OnValidSubmit="#OnValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-row">
<div class="form-group col-md-3">
<SfTextBox #bind-Value="#Contact.AccountName" Placeholder="Account Name"></SfTextBox>
<ValidationMessage For="#(() => Contact.AccountName)"></ValidationMessage>
</div>
</div>
<!-- List of Contact Persons -->
<div class="form-row">
<div class="form-group col-md-10">
<SfGrid DataSource="#Contact.ContactPersons" Toolbar="#(new List<string>() { "Add", "Edit", "Delete", "Cancel", "Update" })" AllowPaging="true">
<GridEditSettings AllowAdding="true" AllowEditing="true" AllowDeleting="true"></GridEditSettings>
<GridColumns>
<GridColumn Field=#nameof(ContactPerson.ID) IsPrimaryKey="true" HeaderText="ID"></GridColumn>
<GridColumn Field=#nameof(ContactPerson.FirstName) HeaderText="First Name"></GridColumn>
<GridColumn Field=#nameof(ContactPerson.MiddleName) HeaderText="Middle Name"></GridColumn>
<GridColumn Field=#nameof(ContactPerson.LastName) HeaderText="Last Name"></GridColumn>
</GridColumns>
</SfGrid>
</div>
</div>
<!-- Post and Back Link -->
<hr />
<div>
<SfButton type="submit" CssClass="e-primary">Sumbit</SfButton>
<SfButton type="button" CssClass="e-danger" #onclick="#OnCancelButton">Cancel</SfButton>
</div>
</EditForm>
}
#code {
[Parameter] public Contact Contact { get; set; }
[Parameter] public EventCallback OnValidSubmit { get; set; }
[Parameter] public EventCallback OnCancelButton { get; set; }
}
The problem I encounter is that Contact.ContactPersons is empty when I pass it to my controller (webapi), and when I break at CreateRecord() which is `` OnValidSubmit, and watch the variable Contact.ContactPersons in VS2019, the variable is empty and says
Unable to Evaluate.
Please help me understand what's going on.
Issue not related to Blazor...
Since ContactPersons is of ICollection<ContactPerson> type, you should instantiate the ContactPersons field with a collection of ContactPerson objects like this:
private Contact contact = new Contact
{
ContactPersons = new List<ContactPerson>()
};

Updating related entities

AppUser identity model:
public virtual ICollection<UserPhones> UserPhones { get; set; }
Using Razor Pages, I call a partial view, like so:
#await Html.PartialAsync("_NameAndID", Model.AppUser)
PageModel:
[BindProperty]
public AppUser AppUser { get; set; }
public IActionResult OnGet()
{
AppUser = _userManager.Users
//.Include(x => x.UserAddresses) //OMITTED BC USING LAZY LOADING
.SingleOrDefaultAsync(x => x.UserName ==
_httpContext.HttpContext.User.Identity.Name).Result;
return Page();
}
Within _NameAndID.cshtml, I explicitly reference a particular telephone from the UserPhones entity. With:
<input type="hidden" asp-for="UserPhones
.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).UserPhoneId" />
//other properties removed for brevity
<div class="rvt-grid__item">
<label asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber">Mobile Phone</label>
<input asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber" autocomplete="tel" />
<span asp-validation-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber"></span>
</div>
At runtime, the explicit mobile phone number is loaded properly. However when posting to public async Task<IActionResult> OnPostAsync() the related AppUser.UserPhones is null. (The problem)
Can you help?
Thank you in advance!!!!
The Reason
The asp-for does not work well for this scenario.
Considering your code in _NameAndID.cshtml :
<input asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber" autocomplete="tel" />
Note the LINQ extension method .SingleOrDefault(...) here. The asp-for here does not know how to get the name for UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber, so it just render it as PhoneNumber. As a result, the rendered html will be :
<input autocomplete="tel" type="text" id="PhoneNumber" name="PhoneNumber" value="">
Let's say someone inputs an value of 911, when posted to server, the payload will be :
PhoneNumber=911
As your page model on server side is :
[BindProperty]
public AppUser AppUser{get;set;}
public IActionResult OnGet()
{
// ...
}
public IActionResult OnPostAsync()
{
return Page();
}
Note the AppUser.UserPhones property is a collection. in other words, AppUser expects a payload like :
UserPhones[0].UserPhoneId=1&UserPhones[0].PhoneNumber=911&UserPhones[1].UserPhoneId=2&UserPhones[1].PhoneNumber=119
However, what you send to the server is :
PhoneNumber=911
So the App.UserPhones will always be null and the AppUser.PhoneNumber property will be 911.
How to Fix
Firstly, in order to bind the UserPhones automatically, I change the type of App.UserPhones to IList<UserPhones> , so that we can use a index syntax
public class AppUser : IdentityUser{
// public virtual ICollection<UserPhones> UserPhones { get; set; }
public virtual IList<UserPhones> UserPhones { get; set; }
}
Secondly, don't use complex query in asp-for, use simple index syntax instead. For example, if you would like to post some UserPhones or post all UserPhones, you can add an index for each field :
#for(var i=0;i <Model.UserPhones.Count(); i++) {
<div class="rvt-grid__item">
<label asp-for="#Model.UserPhones[i].UserPhoneId"></label>
<input asp-for="#Model.UserPhones[i].UserPhoneId"/>
<label asp-for="#Model.UserPhones[i].PhoneNumber"></label>
<input asp-for="#Model.UserPhones[i].PhoneNumber"/>
<span asp-validation-for="#Model.UserPhones[i].PhoneNumber"></span>
</div>
}
In this way, when someone submits the form, AppUser.UserPhones will be the correctly set. Here's a screenshot of demo :

Creating dynamic role-based authorization

I have implemented Identity Role-based authorization but having to manually go to each controller/action and specify individually [Authorize(Roles = "")] very poor extensibility.
How would I be able to create a UI screen, with dynamic role-based authorization, where the "super admin" can configure which role has access to a controller/action?
Something like this:
After a lot of trial and error and a lot of research, I found an adequate answer:(big thanks to Mohen 'mo-esmp' Esmailpour)
Create 2 class:
public class MvcControllerInfo
{
public string Id => $"{AreaName}:{Name}";
public string Name { get; set; }
public string DisplayName { get; set; }
public string AreaName { get; set; }
public IEnumerable<MvcActionInfo> Actions { get; set; }
}
public class MvcActionInfo
{
public string Id => $"{ControllerId}:{Name}";
public string Name { get; set; }
public string DisplayName { get; set; }
public string ControllerId { get; set; }
}
Add another class MvcControllerDiscovery to Services folder to discover all controllers and actions:
public class MvcControllerDiscovery : IMvcControllerDiscovery
{
private List<MvcControllerInfo> _mvcControllers;
private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
public MvcControllerDiscovery(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
{
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
}
public IEnumerable<MvcControllerInfo> GetControllers()
{
if (_mvcControllers != null)
return _mvcControllers;
_mvcControllers = new List<MvcControllerInfo>();
var items = _actionDescriptorCollectionProvider
.ActionDescriptors.Items
.Where(descriptor => descriptor.GetType() == typeof(ControllerActionDescriptor))
.Select(descriptor => (ControllerActionDescriptor)descriptor)
.GroupBy(descriptor => descriptor.ControllerTypeInfo.FullName)
.ToList();
foreach (var actionDescriptors in items)
{
if (!actionDescriptors.Any())
continue;
var actionDescriptor = actionDescriptors.First();
var controllerTypeInfo = actionDescriptor.ControllerTypeInfo;
var currentController = new MvcControllerInfo
{
AreaName = controllerTypeInfo.GetCustomAttribute<AreaAttribute>()?.RouteValue,
DisplayName = controllerTypeInfo.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName,
Name = actionDescriptor.ControllerName,
};
var actions = new List<MvcActionInfo>();
foreach (var descriptor in actionDescriptors.GroupBy(a => a.ActionName).Select(g => g.First()))
{
var methodInfo = descriptor.MethodInfo;
actions.Add(new MvcActionInfo
{
ControllerId = currentController.Id,
Name = descriptor.ActionName,
DisplayName = methodInfo.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName,
});
}
currentController.Actions = actions;
_mvcControllers.Add(currentController);
}
return _mvcControllers;
}
}
IActionDescriptorCollectionProvider provides the cached collection of ActionDescriptor which each descriptor represents an action. Open Startup class and inside Configure method and register MvcControllerDiscovery dependency.
services.AddSingleton<IMvcControllerDiscovery, MvcControllerDiscovery>();
add role controller to manage roles. In Controller folder create RoleController then add Create action:
public class RoleController : Controller
{
private readonly IMvcControllerDiscovery _mvcControllerDiscovery;
public RoleController(IMvcControllerDiscovery mvcControllerDiscovery)
{
_mvcControllerDiscovery = mvcControllerDiscovery;
}
// GET: Role/Create
public ActionResult Create()
{
ViewData["Controllers"] = _mvcControllerDiscovery.GetControllers();
return View();
}
}
Create class RoleViewModel in the Models directory:
public class RoleViewModel
{
[Required]
[StringLength(256, ErrorMessage = "The {0} must be at least {2} characters long.")]
public string Name { get; set; }
public IEnumerable<MvcControllerInfo> SelectedControllers { get; set; }
}
And in View folder add another folder and name it Role then add Create.cshtml view. I used jQuery.bonsai for showing controller and action hierarchy.
#model RoleViewModel
#{
ViewData["Title"] = "Create Role";
var controllers = (IEnumerable<MvcControllerInfo>)ViewData["Controllers"];
}
#section Header {
<link href="~/lib/jquery-bonsai/jquery.bonsai.css" rel="stylesheet" />
}
<h2>Create Role</h2>
<hr />
<div class="row">
<div class="col-md-6">
<form asp-action="Create" class="form-horizontal">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label col-md-2"></label>
<div class="col-md-10">
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">Access List</label>
<div class="col-md-9">
<ol id="tree">
#foreach (var controller in controllers)
{
string name;
{
name = controller.DisplayName ?? controller.Name;
}
<li class="controller" data-value="#controller.Name">
<input type="hidden" class="area" value="#controller.AreaName" />
#name
#if (controller.Actions.Any())
{
<ul>
#foreach (var action in controller.Actions)
{
{
name = action.DisplayName ?? action.Name;
}
<li data-value="#action.Name">#name</li>
}
</ul>
}
</li>
}
</ol>
</div>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="~/lib/jquery-qubit/jquery.qubit.js"></script>
<script src="~/lib/jquery-bonsai/jquery.bonsai.js"></script>
<script>
$(function () {
$('#tree').bonsai({
expandAll: false,
checkboxes: true,
createInputs: 'checkbox'
});
$('form').submit(function () {
var i = 0, j = 0;
$('.controller > input[type="checkbox"]:checked, .controller > input[type="checkbox"]:indeterminate').each(function () {
var controller = $(this);
if ($(controller).prop('indeterminate')) {
$(controller).prop("checked", true);
}
var controllerName = 'SelectedControllers[' + i + ']';
$(controller).prop('name', controllerName + '.Name');
var area = $(controller).next().next();
$(area).prop('name', controllerName + '.AreaName');
$('ul > li > input[type="checkbox"]:checked', $(controller).parent()).each(function () {
var action = $(this);
var actionName = controllerName + '.Actions[' + j + '].Name';
$(action).prop('name', actionName);
j++;
});
j = 0;
i++;
});
return true;
});
});
</script>
}
This should get you to show each action in all controllers in the front-end to customize permission access for whichever role.
If you don't have a class inheriting from identity user you can follow the rest of the steps at the link below to show how to set a role to a specific user. Good luck!
https://github.com/mo-esmp/DynamicRoleBasedAuthorizationNETCore/blob/master/README.md
Hope this helps.
Making roles dynamic has a performance side effect caused by retrieving controls or actions roles in every client's request.
Instead, The recommended way is to keep the authorization roles static (usually they mention it by permission at endpoint level) and then creating a class that can dynamically group these permissions under one label name of the object class instance (usually they called them roles).
This way ensures a higher performance than your approach.

Dynamic form with multiple objects submission in Spring?

I have an Object CreateProjectFormModel as follows (I am using Spring 4).
public class CreateProjectFormModel {
private Project project;
private List<DUser> users;
public CreateProjectFormModel() {
this.project = new Project();
this.users = new ArrayList<DUser>();
}
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}
public List<DUser> getUsers() {
return users;
}
public void setUsers(List<DUser> users) {
this.users = users;
}
}
I am not able to figure out how to create Controller and a corresponding form so that multiple DUser can be submitted at once - can do it if the object does not consist of a collection?
Read this, but I don't know how may users will be added to the project in advance, so cannot fix the users size.
I read through thymeleaf tutorial, but would be interested to know if can do without use of thymeleaf.
Thanks.
The link you posted in the question List<Foo> as form backing object using spring 3 mvc, correct syntax? should provide a solution for you, what is discussed in the comments
I assume that this solution requires having a fixed amount of input
fields, is that correct? What if you have a dynamic number of input
fields?
does not concern the number of users, which doesn't have to be fixed, rather it concerns the fact that that the properties of the object is differing, which I don't believe is your case. So, if your DUser has a property userName, and e.g. your Project has a property name. Your controller method could simply be,
#RequestMapping(value = "/test", method=RequestMethod.POST)
public String processSubmit(CreateProjectFormModel createProjectFormModel) {
...
}
and your form
<form:form action="/form/test" method="post">
<div class="single">
<input type="text" name="project.name"/>
<input type="text" name="users[0].userName"/>
add another user
<input type="submit" value="Save">
</div>
</form:form>
where you will have to provide some effort is to create a javascript function addNewUserInputSection that will add new set of input fields for the users property with an incremented index, e.g.
<form:form action="/form/test" method="post">
<div class="single">
<input type="text" name="project.name"/>
<input type="text" name="users[0].userName"/>
<input type="text" name="users[1].userName"/>
add another user
<input type="submit" value="Save">
</div>
</form:form>
the examples are basic, but should be enough to have you resolve your issue
Although the above answer works, here's an alternate that does not require you to create a wrapper class/ form class.
Model And Controller
public class Foo {
private String name;
private List<Foo> fooList;
public Foo() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFooList() {
return fooList;
}
public void setFooList(String fooList) {
this.fooList = fooList;
}
}
#Controller("/")
public class FooController{
//returns the ModelAttribute fooListWrapper with the view fooForm
#RequestMapping(value = "/FOO", method = RequestMethod.GET)
public String getFooForm(Model model) {
List<Foo> fooList = service.getFooList();
model.addAttribute("fooList", fooList);
return "list_foo"; //name of the view
}
#RequestMapping(value = "/FOO", method = RequestMethod.POST)
public String postFooList(#ModelAttribute("foo")Foo foo, Model model) {
List<Foo> list = foo.getFooList(); // **This is your desired object.
//If you debug this code, you can easily find this is the list of
//all the foo objects that you wanted, provided you pass them properly.
//Check the jsp file to see one of the ways of passing such a list of objects**
//Rest of the code
}
}
JSP View
<form:form id="form" action="<paste-target-url-here>" method="POST" modelAttribute="fooList">
<c:forEach items="${fooList}" varStatus="i">
<form:input path="fooList[${i.index}].name" type="text"/>
<!-- Here you are setting the data in the appropriate index which will be caught in the controller -->
</c:forEach>
<button>submit</button>
</form:form>

In MVC2, how do I validate fields that aren't in my data model?

I am playing with MVC2 in VS 2010 and am really getting to like it. In a sandbox application that I've started from scratch, my database is represented in an ADO.NET entity data model and have done much of the validation for fields in my data model using Scott Guthrie's "buddy class" approach which has worked very well.
However, in a user registration form that I have designed and am experimenting with, I'd like to add a 'confirm email address' or a 'confirm password' field. Since these fields obviously wouldn't exist in my data model, how would I validate these fields client side and server side?
I would like to implement something like 'Html.ValidationMessageFor', but these fields don't exist in the data model. Any help would be greatly appreciated.
I use view models. I don't create the data model instance to persist until the view model is valid.
Below is a simple example. Notice that some of the properties are data models, but the validation properties only exist on this view model.(the base isn't pertinent here)
public class ProblemAddToDepartmentProductView : ViewModel
{
public Problem Problem { get; set; }
public IList<Product> AllProducts { get; set; }
public IList<Department> AllDepartments { get; set; }
public string ProblemId { get; set; }
public string ProblemName { get; set; }
[DisplayName("Choose the product:")]
[Required(ErrorMessage = "Select the Product.")]
public string SelectedProduct { get; set; }
public SelectList GetProducts()
{
var selectList = new SelectList(AllProducts, "Id", "Name");
return selectList;
}
[DisplayName("Choose the department using this problem for that product:")]
[Required(ErrorMessage = "Select the Department.")]
public string SelectedDepartment { get; set; }
public SelectList GetDepartments()
{
var selectList = new SelectList(AllDepartments, "Id", "Name");
return selectList;
}
internal class ProductSelect
{
public Guid Id { get; set; }
public string Name { get; set; }
}
}
It will also help to see it wired on the page:
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%= Html.LabelFor(x => x.SelectedProduct) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(x => x.SelectedProduct, Model.GetProducts(),"--Select One--") %>
<%= Html.ValidationMessageFor(x => x.SelectedProduct)%>
</div>
<div class="editor-label">
<%= Html.LabelFor(x => x.SelectedDepartment) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(x => x.SelectedDepartment, Model.GetDepartments(),"--Select One--") %>
<%= Html.ValidationMessageFor(x => x.SelectedDepartment)%>
</div>
<p>
<input type="submit" value="Add Selected" />
</p>
</fieldset>
I also do this so the model will have these values if the validation fails, to pull back in the needed data for the drop downs:
p.ProblemId) %>
<%= Html.HiddenFor(p => p.ProblemName) %>
Client-Side:
Using Javascript Validation
and/or
Server-Side:
Validate in Controller (Using FormCollection) - or
Create "CustomViewModel" Class that encapsulates all validation strongly type your view - or
You could add two string properties to your Model. Doing so will allow you to populate an instance of your model in a Controller and validate appropriately in your Model/s...