Multiple validation in one rule using FluentValidation - c#

I'm tring to build a validation rule using FluentValidation lib.
When I use the first part of the code bellows, it works as expected; but when I use the second part, only the first validation is executed, ignoring the last two validation rules.
The second part is not supposed to work? Am I doing something wrong?
// first part -- it works
RuleFor(c => c.Field).RequiredField().WithMessage(CustomMsg1);
RuleFor(c => c.Field).ValidateField().WithMessage(CustomMsg2);
RuleFor(c => c.Field).IsNumeric().WithMessage(CustomMsg3);
// second part -- validates only the first rule
RuleFor(c => c.Field)
.RequiredField().WithMessage(CustomMsg1)
.ValidateField().WithMessage(CustomMsg2)
.IsNumeric().WithMessage(CustomMsg3);

You can specify FluentValidator's cascading behavior with the CascadeMode enum.
You can read more about it here.
The default behavior should be the one you need, but if it works differently without setting the CascadeMode, maybe the default value is globally set somewhere in your project.
Configuring the default value globally looks like this:
ValidatorOptions.Global.CascadeMode = CascadeMode.Stop;
Your code with the default value set above should look something like this:
RuleFor(c => c.Field)
.Cascade(CascadeMode.Continue)
.RequiredField().WithMessage(CustomMsg1)
.ValidateField().WithMessage(CustomMsg2)
.IsNumeric().WithMessage(CustomMsg3);

Related

ASP.NET Core : AddAreaPageRoute - parameter more than one time in the route

What I am trying to do is something like this:
services
.AddRazorPages()
.AddRazorPagesOptions(options =>
{
options.Conventions.AddAreaPageRoute("Products", "/DownloadDoc", "Products/{keycode}/{keycode}-document.pdf");
});
So if keycode was productTest, the url generated should be:
Products/productTest/productTest-document.pdf
Not sure if it is possible or not?
Currently it throws this error:
RoutePatternException: The route parameter name 'keycode' appears more than one time in the route template.
I have even tried things like this:
options.Conventions.AddAreaPageRoute("Products", "/DownloadDoc", "Products/{keycode2:keycode}/{keycode}-document.pdf");
actually, the problem is the naming problem.
The URL format doesn't support multi able same parameters name.
Can you try like this
options.Conventions.AddAreaPageRoute("Products", "/DownloadDoc", "Products/{keycode}/{keycode2}-document.pdf");
According to your route rule Products/{keycode}/{keycode}-document.pdf, this rule means it will match the two part for the url with the same parameter. This is not expected inside the asp.net core rule mapping rule.
If you think this two parameter should be the same, you could make a check by using a specific middleware or else.
If you make sure this two parameters will be the same, I think just one keycode is enough.

How can I add a content type to an index programmatically?

I'm trying to programmatically create indexes corresponding to several types, so I can search through items of specific types in my controllers. (I tried using the type keyword in Lucene, but that seems to not work at all no matter what I do, so I changed my approach.)
However, I can't figure out how to tell Orchard to include a specific type in an index in a migration. I tried using:
var typeIndexing = ContentDefinitionManager.GetTypeDefinition(contentType)
.Settings.GetModel<TypeIndexing>();
typeIndexing.List = typeIndexing.List.Concat(indexName.Yield()).ToArray();
but that just returns null as the result of GetTypeDefinition().
I'm looking at using:
ContentDefinitionManager.AlterTypeDefinition(contentType, builder => {
builder.WithSetting("TypeIndexing.Indexes", indexName);
});
but that seems like it replaces the previous configured index, if it works at all (EDIT: nope), and I don't want to clobber the existing setting. (A different person on the team is handling our setup recipe.)
Is there any place where I could touch that setting and have it be stored and actually used by Orchard outside the recipe file?
To illustrate what I'm trying to accomplish using the analogous admin UI changes, under Content Definition > [Content Type Name] > Edit:
Before:
After:
What you're looking for is the Indexed() extension method. It accepts the indexes you want to use on the content type.
ContentDefinitionManager.AlterTypeDefinition(nameof(contentType),type =>
type
.Indexed("FirstIndex", "SecondIndex"));

Model Validation in Web API

I have to validate three things when a consumer of my API tries to do an update on a customer.
Prevent the customer to be updated if:
The first name or last name are blank
For a certain country, if the customer's inner collection of X is empty, then throw an exception. X is hard to explain, so just assume it's some collection. For all other countries, X doesn't apply / will always be empty. But if it's a certain country, then X is required. So it's almost a conditional required attribute. A customer belongs to a country, so it's figured out from the JSON being sent.
Prevent the customer from being updated if some conditions in the database are true.
So basically i'm stuck with the following problem, and I wanted some advice on the most appropriately way to solve it:
Do I create an Action Filter to do the validation on the customer entity before the saving takes place? Or would it be better to create custom validation attribute derived from ValidationAttribute and override the IsValid member function.
Basically a question of saying
if (first name is empty, if x, if y, etc) vs (!ModelState.IsValid)
And then using IsValid to cause the custom attributes to work.
It seems like validation attributes are best for "simple" validation, i.e. required field. But once you start getting into things like "I need to look at my database, or analyze the http request header for custom values, and based on that, invalid = false" then it almost seems wrong to do this sort of stuff so close to the entity.
Thoughts?
Thanks!
I like FluentValidation a lot: https://github.com/JeremySkinner/FluentValidation
As you mentioned built-in validation attributes are limited. For complex validations you had better implement your own attributes or use a library like this.
One thing I like about FluentValidation is that it performs at model-level rather than field-level, meaning that you can use related fields' values for validation. For example
RuleFor(customer => customer.Discount).NotEqual(0).When(customer => customer.HasDiscount);
(Code excerpt taken from project's Wiki page)
It's also extensible so you can develop your own custom validators on top of this library as well.

What is the difference between #Html.ValueFor(x=>x.PropertyName) and #Model.PropertyName

#Html.ValueFor(x=>x.PropertyName)
#Model.PropertyName
It seems like these two Razor commands do the exact same thing. Is there any special circumstance or benefit of using one over the other?
#Html.ValueFor(x => x.PropertyName) invokes a lot a code and reflection under the hood.
It will allow you to customize the way the value is presented, and then have a consistent format across your whole site.
For example, if your property is decorated with DisplayFormatAttribute.
#Model.PropertyName is literally getting the value of the property directly, calling ToString() on it, and HTML escaping the result. No other formatting will take place.
To illustrate, you might see this:
[DisplayFormat(DataFormatString="{0:C}")]
public decimal PropertyName = 1234.56;
#Html.ValueFor(x => x.PropertyName) => "£1,234.56"
#Model.PropertyName => "1234.56"
ValueFor will invoke the template that exists for rendering the type that the property has. By default this template may be as simple as ToString(), but you can define anything as the template.
#Model.PropertyName will simply present the value as string.

How to solve cast issues in ValidationRule classes' properties?

I need to create a few tests for the user roles in a web application. To minimize the description, one of the tests involves checking if a menu entry is displayed or not for an user.
For this test, I use a table called UserRoles, that looks like this:
sUserName bDoesntHaveMenuX
User1 1
User2 0
User3 1
bDoesntHaveMenuX is of type bit.
I have a class derived from ValidationRule that checks if a certain text is present in a page, based on a XPath expression to locate the node where to look for the text.
The public properties of this class are:
string XPathExpression
string Text
bool FailIfFound
The last one dictates if the rule should fail if the text is found or not found.
In the test I added a datasource for the table mentioned in the beginning, called DS.
For the request I'm interested in I added a new instance of my validation rule class, with the following values:
Text=MenuX
XPathExpression=//div[#id='menu']//td
FailIfFound={{DS.UserRoles.bDoesntHaveMenuX}}
Unfortunately, this doesn't work.
The reason seems to be that the data binding process creates a context variable
DS.UserRoles.bDoesntHaveMenuX has the value "False" or "True". The value is a string, so the binding results in a casting error.
My options, as far as I can think of, are:
Change the validation rule to accept strings for FailIfFound. Not a valid
option, for 2 reasons: it's a hack and the same rule is used in
other places.
Make a new validation rule that will use the above mentioned one,
and implement the FailIfFound as string. I also don't like this, for
the same reason as above. It's a hack.
Make the test coded and do the proper cast before passing the data
to the validation rule. I don't like this one because I prefer to
have the test as coded only if there is no other way.
Which brings me to the question. Is there another way?
Thank you.
So the fundamental issue is that you have no control over how the data-binding treats the 'bit' data type, and it's getting converted to string instead of bool.
The only solution I can think of (which is sadly still a bit of a hack, but not so egregious as changing FailIfFound to string) is to create a WebTestPlugin, and in the PreRequestDataBinding or PreRequest event, convert the value from string to bool. Don't forget to add the plugin to your test(s) (easy mistake I have made).
Then when the validation rule is created it should pick up the nice new bool value and work correctly.
e.g.
string val = e.WebTest.Context["DS.UserRoles.bDoesntHaveMenuX"].ToString();
e.WebTest.Context["DS.UserRoles.bDoesntHaveMenuX"] = (val == "True");
I didn't actually try this... hope it works.
EDIT: round two... a better solution
Change the FailIfFound property to string (in a subclass as you mentioned), so it can work properly with data-binding.
Implement a TypeConverter that provides a dropdown list of valid values for the property in the rule's PropertyGrid (True, False), so in the GUI it looks identical to the rule having FailIfFound as a bool. You can still type your own value into the box when necessary (e.g. for data-binding).
Add the path of the .dll containing the TypeConverter code to your test project's References section.
This is what I have started doing and it is much more satisfying than having to type 'True' or 'False' in the property's edit box.

Categories

Resources