Web api HTTP 500 (Internal Server Error) - asp.net-mvc

Hello I have an error 500 (internal server error) when I run the code below. My issue is that I have no trace at all of the error. It seems that visual studio is unable to catch it.
The following code returns a Candidate if I try to add pers to candidate the code fail and i get error 500. The thing is PersonAddressDescription implement AddressDescription is inheritance the problem ?
public class CheckController : ApiController
{
public Candidate Get()
{
PersonAddressDescription pers = new PersonAddressDescription();
Candidate candidate = new Candidate();
//IF I REMOVE THIS NO PROBLEM
candidate.address = pers;
return candidate;
}
}
AddressDescription class
/// <remarks/>
[System.Xml.Serialization.XmlIncludeAttribute(typeof(CompanyAddressDescription))]
[System.Xml.Serialization.XmlIncludeAttribute(typeof(PersonAddressDescription))]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.17626")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.crif-online.ch/webservices/crifsoapservice/v1.00")]
public abstract partial class AddressDescription : object, System.ComponentModel.INotifyPropertyChanged {
private Location locationField;
private ContactItem[] contactItemsField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public Location location {
get {
return this.locationField;
}
set {
this.locationField = value;
this.RaisePropertyChanged("location");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("contactItems", Order=1)]
public ContactItem[] contactItems {
get {
return this.contactItemsField;
}
set {
this.contactItemsField = value;
this.RaisePropertyChanged("contactItems");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
PersonAddressDescription class that implement AddressDescription
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.17626")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.crif-online.ch/webservices/crifsoapservice/v1.00")]
public partial class PersonAddressDescription : AddressDescription {
private string firstNameField;
private string lastNameField;
private string maidenNameField;
private Sex sexField;
private bool sexFieldSpecified;
private string birthDateField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public string firstName {
get {
return this.firstNameField;
}
set {
this.firstNameField = value;
this.RaisePropertyChanged("firstName");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=1)]
public string lastName {
get {
return this.lastNameField;
}
set {
this.lastNameField = value;
this.RaisePropertyChanged("lastName");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=2)]
public string maidenName {
get {
return this.maidenNameField;
}
set {
this.maidenNameField = value;
this.RaisePropertyChanged("maidenName");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=3)]
public Sex sex {
get {
return this.sexField;
}
set {
this.sexField = value;
this.RaisePropertyChanged("sex");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool sexSpecified {
get {
return this.sexFieldSpecified;
}
set {
this.sexFieldSpecified = value;
this.RaisePropertyChanged("sexSpecified");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=4)]
public string birthDate {
get {
return this.birthDateField;
}
set {
this.birthDateField = value;
this.RaisePropertyChanged("birthDate");
}
}
}

I suspect that the object you retrieved (addResp) contains circular references somewhere in its object graph. Circular references cannot be JSON serialized.
For example try putting the following code inside your controller to test what happens when you attempt to JSON serialize this instance:
TypeIdentifyAddressResponse addResp = ws.identifyAddress("test");
string json = JsonConvert.SerializeObject(addResp);
UPDATE:
It seems that AddressDescription is an abstract class and your actual instance is PersonAddressDescription. You need to indicate that to the serializer by using the [KnownType] attribute:
[KnownType(typeof(PersonAddressDescription))]
[KnownType(typeof(CompanyAddressDescription))]
...
public abstract partial class AddressDescription : object, System.ComponentModel.INotifyPropertyChanged {
{
...
}
As an alternative if you don't want to further pollute your (already polluted) domain models with other attributes you could also define the known type inside your WebApiConfig.cs:
config.Formatters.XmlFormatter.SetSerializer<Candidate>(
new DataContractSerializer(typeof(Candidate),
new Type[] { typeof(PersonAddressDescription) }));

Related

Vaadin: Bind Enum values to String in Vaadin 8

I’m working on upgrading our application vaadin version from 7.7.24 to 8.13.3. We’ve completed all the dependency issues and i’m able to start the application in locally.
We have a textbox that is showing up the Event data.
Here is the class file that i'm using:
#Entity
#Table(name = "changelog")
public class ChangelogEvent extends BaseEntity
{
#Column(name = "remote_ip")
private String remoteIp;
#Column(name = "remote_host")
private String remoteHost;
#Column(name = "event")
#Enumerated(EnumType.ORDINAL)
private ChangelogEventType eventType;
#Column(name = "entity_type")
private String entityType;
public ChangelogEvent()
{
}
public ChangelogEvent(String remoteIp, String remoteHost, ChangelogEventType eventType)
{
this.remoteIp = remoteIp;
this.remoteHost = remoteHost;
this.eventType = eventType;
}
public String getRemoteIp()
{
return remoteIp;
}
public void setRemoteIp(String remoteIp)
{
this.remoteIp = remoteIp;
}
public ChangelogEventType getEventType()
{
return eventType;
}
public void setEventType(ChangelogEventType eventType)
{
this.eventType = eventType;
}
public String getRemoteHost()
{
return remoteHost;
}
public void setRemoteHost(String remoteHost)
{
this.remoteHost = remoteHost;
}
public String getEntityType()
{
return entityType;
}
public void setEntityType(String entityType)
{
this.entityType = entityType;
}
}
And here is my ChangelogEventType.java file that defined ChangelogEventType enum:
public enum ChangelogEventType
{
CREATED("Created"),
UPDATED("Updated"),
DELETED("Deleted"),
LOGIN("Login"),
LOGOUT("Logout"),
LOGIN_RETRY("Login Retry"),
ACCOUNT_LOCKED("Account Locked"),
PASSWORD_EXPIRED("Password Expired"),
PASSWORD_CHANGED("Password Changed");
private String text;
ChangelogEventType(String text)
{
this.text = text;
}
public String getText()
{
return text;
}
public static ChangelogEventType fromString(String text)
{
if (text != null)
{
for (ChangelogEventType event : ChangelogEventType.values())
{
if (text.equalsIgnoreCase(event.text))
{
return event;
}
}
}
return null;
}
}
Here is the code that i'm using for binding the values into textfield.
eventType = createTextField("Event", COLUMN_WIDTH);
binder.forField(eventType)
.withNullRepresentation("None")
.bind(ChangelogEvent::getEventType, ChangelogEvent::setEventType);
Is there any way to bind the Enum to textbox ?
You need to write custom converter and use it in Binder using withConverter method, in your case something like:
class StringToChangelogEventTypeConverter implements Converter<String, ChangelogEventType> {
#Override
public Result<ChangelogEventType> convertToModel(String fieldValue, ValueContext context) {
// Produces a converted value or an error
ChangelogEventType event = ChangelogEventType.fromString(fieldValue);
if (event != null) {
// ok is a static helper method that creates a Result
return Result.ok(ChangelogEventType.fromString(fieldValue));
} else {
// error is a static helper method that creates a Result
return Result.error("Please enter a number");
}
}
#Override
public String convertToPresentation(ChangelogEventType event, ValueContext context) {
// Converting to the field type should always succeed,
// so there is no support for returning an error Result.
return event.getText();
}
}

ResultSet mapping to object dynamically in dropwizard

I was trying to map ResultSet data to an object and returning it. Here is how i'm mapping data to an object. Now i'm having only 7 columns in resultset so this is working fine but what if i'm having 20 or 30 columns. How can i map dynamically those columns.
public class ProductsWrapperMapper implements ResultSetMapper<ProductsWrapper> {
public ProductsWrapper map(int i, ResultSet resultSet,
StatementContext statementContext) throws SQLException {
ProductsWrapper product = new ProductsWrapper();
if ((isColumnPresent(resultSet,"a_productid"))) {
product.setId(resultSet.getInt("a_productid"));
}
if ((isColumnPresent(resultSet,"a_productname"))) {
product.setProductName(resultSet.getString("a_productname"));
}
if ((isColumnPresent(resultSet,"a_productlink"))) {
product.setLink(resultSet.getString("a_productlink"));
}
if ((isColumnPresent(resultSet,"a_productimagelink"))) {
product.setImageLink(resultSet.getString("a_productimagelink"));
}
if ((isColumnPresent(resultSet,"a_websiteid"))) {
product.setWebsiteId(resultSet.getInt("a_websiteid"));
}
if ((isColumnPresent(resultSet,"a_productidentification"))) {
product.setProductIdentification(resultSet
.getString("a_productidentification"));
}
if ((isColumnPresent(resultSet,"a_adddate"))) {
product.setAddDate(resultSet.getString("a_adddate"));
}
return product;
}
public boolean isColumnPresent(ResultSet resultSet,String column) {
try {
#SuppressWarnings("unused")
int index = resultSet.findColumn(column);
return true;
} catch (SQLException e) {
// TODO Auto-generated catch block
return false;
}
}
}
Below one is my class which i was returning the object from mapper class above.
#JsonInclude(Include.NON_NULL)
public class ProductsWrapper {
private int id;
private String productName;
private String link;
private String imageLink;
private int websiteId;
private String productIdentification;
private String addDate;
int getWebsiteId() {
return websiteId;
}
public void setWebsiteId(int websiteId) {
this.websiteId = websiteId;
}
public String getProductIdentification() {
return productIdentification;
}
public void setProductIdentification(String productIdentification) {
this.productIdentification = productIdentification;
}
public String getAddDate() {
return addDate;
}
public void setAddDate(String addDate) {
this.addDate = addDate;
}`enter code here`
public ProductsWrapper(int id) {
this.setId(id);
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getImageLink() {
return imageLink;
}
public void setImageLink(String imageLink) {
this.imageLink = imageLink;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
You can also try Jdbi-folder. It automatically takes care of dynamic bynding and also it provides one to many mapping relationship.
You can add Rosetta as a mapper for your JDBI result sets (it also works for bindings). Have a look at the advanced features to map column names with underscores to snake snake case java names.
Beware that there is no warning message if Rosetta is unable to map a value: any missed property in the target bean will just be empty. I found that my database returned column names in capital letters, therefore the LowerCaseWithUnderscoresStrategy in the example didn't work for me. I created a UpperCaseWithUnderscoresStrategy.
To skip writing getters and setters in ProductsWrapper have a look at Lombok's #Data annotation.

The query results cannot be enumerated more than once, MVC WebGrid issue

Error:
System.InvalidOperationException: The query results cannot be enumerated more than once.
Code:
Controller:
namespace EmployeeAttendance_app.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Precise Technology Consultants";
var DataContext = new EmployeeAtdDataContext();
//var EmployeeAtd = DataContext.GetAttendance_Sp();
IEnumerable<GetAttendance_SpResult> EmployeeAtd = DataContext.GetAttendance_Sp();
return View(EmployeeAtd);
}
View:
#using EmployeeAttendance_app.Models
<div>
#{
var grid = new WebGrid(Model, defaultSort: "EmplID");
}
#grid.GetHtml()
</div>
Models:
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.237
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace EmployeeAttendance_app.Models
{
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Data;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;
using System.ComponentModel;
using System;
[global::System.Data.Linq.Mapping.DatabaseAttribute(Name="a1")]
public partial class EmployeeAtdDataContext : System.Data.Linq.DataContext
{
private static System.Data.Linq.Mapping.MappingSource mappingSource = new AttributeMappingSource();
#region Extensibility Method Definitions
partial void OnCreated();
#endregion
public EmployeeAtdDataContext() :
base(global::System.Configuration.ConfigurationManager.ConnectionStrings["a1ConnectionString"].ConnectionString, mappingSource)
{
OnCreated();
}
public EmployeeAtdDataContext(string connection) :
base(connection, mappingSource)
{
OnCreated();
}
public EmployeeAtdDataContext(System.Data.IDbConnection connection) :
base(connection, mappingSource)
{
OnCreated();
}
public EmployeeAtdDataContext(string connection, System.Data.Linq.Mapping.MappingSource mappingSource) :
base(connection, mappingSource)
{
OnCreated();
}
public EmployeeAtdDataContext(System.Data.IDbConnection connection, System.Data.Linq.Mapping.MappingSource mappingSource) :
base(connection, mappingSource)
{
OnCreated();
}
public System.Data.Linq.Table<EmployeeAtd> EmployeeAtds
{
get
{
return this.GetTable<EmployeeAtd>();
}
}
[global::System.Data.Linq.Mapping.FunctionAttribute(Name="dbo.GetAttendance_Sp")]
public ISingleResult<GetAttendance_SpResult> GetAttendance_Sp()
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())));
return ((ISingleResult<GetAttendance_SpResult>)(result.ReturnValue));
}
}
[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.V_EmployeeAtd")]
public partial class EmployeeAtd
{
private string _EmplID;
private string _EmplName;
private string _RecDate;
private string _RecTime;
private string _DeptName;
public EmployeeAtd()
{
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_EmplID", DbType="Char(8) NOT NULL", CanBeNull=false)]
public string EmplID
{
get
{
return this._EmplID;
}
set
{
if ((this._EmplID != value))
{
this._EmplID = value;
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_EmplName", DbType="NVarChar(40) NOT NULL", CanBeNull=false)]
public string EmplName
{
get
{
return this._EmplName;
}
set
{
if ((this._EmplName != value))
{
this._EmplName = value;
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_RecDate", DbType="Char(10)")]
public string RecDate
{
get
{
return this._RecDate;
}
set
{
if ((this._RecDate != value))
{
this._RecDate = value;
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_RecTime", DbType="Char(5)")]
public string RecTime
{
get
{
return this._RecTime;
}
set
{
if ((this._RecTime != value))
{
this._RecTime = value;
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_DeptName", DbType="NVarChar(50)")]
public string DeptName
{
get
{
return this._DeptName;
}
set
{
if ((this._DeptName != value))
{
this._DeptName = value;
}
}
}
}
public partial class GetAttendance_SpResult
{
private string _EmplID;
private string _EmplName;
private string _RecDate;
private string _RecTime;
private string _DeptName;
public GetAttendance_SpResult()
{
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_EmplID", DbType="Char(8) NOT NULL", CanBeNull=false)]
public string EmplID
{
get
{
return this._EmplID;
}
set
{
if ((this._EmplID != value))
{
this._EmplID = value;
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_EmplName", DbType="NVarChar(40) NOT NULL", CanBeNull=false)]
public string EmplName
{
get
{
return this._EmplName;
}
set
{
if ((this._EmplName != value))
{
this._EmplName = value;
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_RecDate", DbType="Char(10)")]
public string RecDate
{
get
{
return this._RecDate;
}
set
{
if ((this._RecDate != value))
{
this._RecDate = value;
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_RecTime", DbType="Char(5)")]
public string RecTime
{
get
{
return this._RecTime;
}
set
{
if ((this._RecTime != value))
{
this._RecTime = value;
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_DeptName", DbType="NVarChar(50) NOT NULL", CanBeNull=false)]
public string DeptName
{
get
{
return this._DeptName;
}
set
{
if ((this._DeptName != value))
{
this._DeptName = value;
}
}
}
}
}
#pragma warning restore 1591
I'm newbie to MVC 3 and Linq to SQL, trying to display data in GRID using WebGrid class but getting error. I have used Linq to Sql class and dropped SP and table into it.
Change you View to accept list of GetAttendance_SpResult bcoz you are passing this model from your controller
#model IEnumerable<GetAttendance_SpResult>
Try converting ur IEnumerable to List. because When you use .ToList() on IEnumerable, all the items in the IEnumerable saved as a List. and when u work with IEnumerable item is accessed from your database
#{
var grid = new WebGrid(Model.ToList(), defaultSort: "EmplID");
}
#grid.GetHtml()

Get custom attribute for parameter when model binding

I've seen a lot of similar posts on this, but haven't found the answer specific to controller parameters.
I've written a custom attribute called AliasAttribute that allows me to define aliases for parameters during model binding. So for example if I have: public JsonResult EmailCheck(string email) on the server and I want the email parameter to be bound to fields named PrimaryEmail or SomeCrazyEmail I can "map" this using the aliasattribute like this: public JsonResult EmailCheck([Alias(Suffix = "Email")]string email).
The problem: In my custom model binder I can't get a hold of the AliasAttribute class applied to the email parameter. It always returns null.
I've seen what the DefaultModelBinder class is doing to get the BindAttribute in reflector and its the same but doesn't work for me.
Question: How do I get this attribute during binding?
AliasModelBinder:
public class AliasModelBinder : DefaultModelBinder
{
public static ICustomTypeDescriptor GetTypeDescriptor(Type type)
{
return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var descriptor = GetTypeDescriptor(bindingContext.ModelType);
/*************************/
// this next statement returns null!
/*************************/
AliasAttribute attr = (AliasAttribute)descriptor.GetAttributes()[typeof(AliasAttribute)];
if (attr == null)
return null;
HttpRequestBase request = controllerContext.HttpContext.Request;
foreach (var key in request.Form.AllKeys)
{
if (string.IsNullOrEmpty(attr.Prefix) == false)
{
if (key.StartsWith(attr.Prefix, StringComparison.InvariantCultureIgnoreCase))
{
if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
return request.Form.Get(key);
}
}
else if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
if (attr.HasIncludes)
{
foreach (var include in attr.InlcludeSplit)
{
if (key.Equals(include, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(include);
}
}
}
}
return null;
}
}
AliasAttribute:
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class AliasAttribute : Attribute
{
private string _include;
private string[] _inlcludeSplit = new string[0];
public string Prefix { get; set; }
public string Suffix { get; set; }
public string Include
{
get
{
return _include;
}
set
{
_include = value;
_inlcludeSplit = SplitString(_include);
}
}
public string[] InlcludeSplit
{
get
{
return _inlcludeSplit;
}
}
public bool HasIncludes { get { return InlcludeSplit.Length > 0; } }
internal static string[] SplitString(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
return (from piece in original.Split(new char[] { ',' })
let trimmed = piece.Trim()
where !string.IsNullOrEmpty(trimmed)
select trimmed).ToArray<string>();
}
}
Usage:
public JsonResult EmailCheck([ModelBinder(typeof(AliasModelBinder)), Alias(Suffix = "Email")]string email)
{
// email will be assigned to any field suffixed with "Email". e.g. PrimaryEmail, SecondaryEmail and so on
}
Gave up on this and then stumbled across the Action Parameter Alias code base that will probably allow me to do this. It's not as flexible as what I started out to write but probably can be modified to allow wild cards.
what I did was make my attribute subclass System.Web.Mvc.CustomModelBinderAttribute which then allows you to return a version of your custom model binder modified with the aliases.
example:
public class AliasAttribute : System.Web.Mvc.CustomModelBinderAttribute
{
public AliasAttribute()
{
}
public AliasAttribute( string alias )
{
Alias = alias;
}
public string Alias { get; set; }
public override IModelBinder GetBinder()
{
var binder = new AliasModelBinder();
if ( !string.IsNullOrEmpty( Alias ) )
binder.Alias = Alias;
return binder;
}
}
which then allows this usage:
public ActionResult Edit( [Alias( "somethingElse" )] string email )
{
// ...
}

Fluent validation with dynamic message

I am trying to building custom validation with dynamic message in fluent validation library.
For example :
public class CreateProcessValidator : AbstractValidator<CreateProcessVM>
{
public CreateProcessValidator()
{
RuleFor(x => x.ProcessFile).Must((x,e) => IsProcessFileValid(x.ProcessFile))).WithMessage("Parse failed with error : {0}");
}
public bool IsProcessFileValid(HttpPostedFileBase file)
{
var errorMessage = "..." // pass result to validaton message ?
// logic
return false;
}
}
Is here any workaround how to pass validation result ?
Thanks
Have you tried something like this?
public class IsProcessFileValid : PropertyValidator
{
public IsProcessFileValid(): base("{ValidationMessage}") {}
protected override IsValid(PropertyValidatorContext context)
{
if (!IsProcessFileValid1(context))
context.MessageFormatter.AppendArgument("ValidationMessage",
"Custom validation message #1");
if (!IsProcessFileValid2(context))
context.MessageFormatter.AppendArgument("ValidationMessage",
"Custom validation message #2");
// ...etc
return true;
}
private bool IsProcessFileValid1(PropertyValidatorContext context)
{
// logic
return false;
}
private bool IsProcessFileValid2(PropertyValidatorContext context)
{
// logic
return false;
}
// ...etc
}
With extension method:
public static class IsProcessFileValidExtensions
{
public static IRuleBuilderOptions<T, object> MustBeValidProcessFile<T>
(this IRuleBuilder<T, object> ruleBuilder)
{
return ruleBuilder.SetValidator(new IsProcessFileValid());
}
}
... and then use it without a custom WithMessage:
public CreateProcessValidator()
{
RuleFor(x => x.ProcessFile).MustBeValidProcessFile();
}
By creating a custom PropertyValidator, you can encapsulate the default validation message within that class and make it dynamic. However you must not use the .WithMessage extension when declaring the RuleFor, because that would override the default validation message which you customized directly inside the PropertyValidator.
There's no way to do that. I would split the complex validation method you currently have into smaller methods (IsProcessFileValid1, IsProcessFileValid2, IsProcessFileValid3, ...) so that you could have more fine grained control over the error message. Also each method will be responsible for validating only once concern making them more reusable (single responsibility):
RuleFor(x => x.ProcessFile)
.Must(IsProcessFileValid1)
.WithMessage("Message 1")
.Must(IsProcessFileValid2)
.WithMessage("Message 2")
.Must(IsProcessFileValid3)
.WithMessage("Message 3");
Also notice how I simplified the lambda as the method could directly be passed to Must as argument.
Here is how I solved it. Tested with FluentValidation v8.5.0
class EmptyValidationMessage : IStringSource
{
public string ResourceName => null;
public Type ResourceType => null;
public string GetString(IValidationContext context)
{
return string.Empty;
}
public static readonly EmptyValidationMessage Instance = new EmptyValidationMessage();
}
public class MyPropValidator : PropertyValidator
{
public MyPropValidator() : base(EmptyValidationMessage.Instance)
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
// if not valid
Options.ErrorMessageSource = new StaticStringSource("my message");
// you can do LanguageStringSource, LazyStringSource, LocalizedStringSource, etc
// example with localized string (https://github.com/clearwaterstream/LocalizedString.FluentValidation)
Options.ErrorMessageSource = new LocalizedStringSource("my message").InFrench("moi message");
return false;
}
}
Faced the same issue, while trying to insert exception message into WithMessage().
It worked with the method overload taking Func<T, string> messageProvider as parameter.
Here is the solution presented on the posters example (working code, FluentValidation v 9.1):
public class CreateProcessVM
{
public object ProcessFile { get; set; }
}
public class CreateProcessValidator : AbstractValidator<CreateProcessVM>
{
public CreateProcessValidator()
{
var message = "Something went wrong.";
RuleFor(x => x.ProcessFile)
.Must((x, e) => IsProcessFileValid(x.ProcessFile, out message))
// .WithMessage(message); will NOT work
.WithMessage(x => message); //Func<CreateProcessVM, string> as parameter
}
public bool IsProcessFileValid(object file, out string errorMessage)
{
errorMessage = string.Empty;
try
{
Validate(file);
return true;
}
catch (InvalidOperationException e)
{
errorMessage = e.Message;
return false;
}
}
private void Validate(object file)
{
throw new InvalidOperationException("File of type .custom is not allowed.");
}
}
And a test demonstrating that we really get the exception message in the error message:
[Fact]
public void Test()
{
var validator = new CreateProcessValidator();
var result = validator.Validate(new CreateProcessVM());
Assert.False(result.IsValid);
Assert.Equal("File of type .custom is not allowed.", result.Errors[0].ErrorMessage);
}

Resources