Issue with Web Api Custom Model Binder in MVC4 - asp.net-mvc-4

I am using Mvc4 with WebApi.
I am using Dto objects for the webApi.
I am having enum as below.
public enum Status
{
[FlexinumDefault]
Unknown = -1,
Active = 0,
Inactive = 100,
}
Dto structure is as follows.
[DataContract]
public class abc()
{
[DataMemebr]
[Required]
int Id{get;set;}
[DataMember]
[Required]
Status status{get;set}
}
I have created Custom Model Binder which will validate the enum(status) property in the dto object and return false if the enum value is not passed.
if the status enum property is not passed in the dto object,we should throw exception
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
{
var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (input != null && !string.IsNullOrEmpty(input.AttemptedValue))
{
if (bindingContext.ModelType == typeof(Enum))
{
//var actualValue = null;
var value = input.RawValue;
in the api controller,i have action method like
public void Create([FromUri(BinderType = typeof(EnumCustomModelBinder))]abcdto abc)
{
In global.asax.cs
i have set like
GlobalConfiguration.Configuration.BindParameter(typeof(Enum), new EnumCustomModelBinder());
the issue i am facing is the custombinder
var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
,the input value is coming as null.
Please sugggest

I found the solution
This works fine,but the default implementation of model binder is missing.
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var json = actionContext.Request.Content.ReadAsStringAsync().Result;
if (!string.IsNullOrEmpty(json))
{
var jsonObject = (JObject) Newtonsoft.Json.JsonConvert.DeserializeObject(json);
var jsonPropertyNames = jsonObject.Properties().Select(p => p.Name).ToList();
var requiredProperties = bindingContext.ModelType.GetProperties().Where(p =>p.GetCustomAttributes(typeof(RequiredAttribute),
false).Any()).ToList();
var missingProperties = requiredProperties.Where(bindingProperty => !jsonPropertyNames.Contains(bindingProperty.Name)).ToList();
if (missingProperties.Count > 0)
{
missingProperties.ForEach(
prop =>
{
if (prop.PropertyType.IsEnum)
actionContext.ModelState.AddModelError(prop.Name, prop.Name + " is Required");
});
}
var nullProperties = requiredProperties.Except(missingProperties).ToList();
if (nullProperties.Count > 0)
{
nullProperties.ForEach(p =>
{
var jsonvalue = JObject.Parse(json);
var value = (JValue)jsonvalue[p.Name];
if (value.Value == null)
{
actionContext.ModelState.AddModelError(p.Name, p.Name + " is Required");
}
});
}
}
// Now we can try to eval the object's properties using reflection.
return true;
}

Related

Upgrade Solution to use FluentValidation Ver 10 Exception Issue

Please I need your help to solve FluentValidation issue. I have an old desktop application which I wrote a few years ago. I used FluentValidation Ver 4 and Now I'm trying to upgrade this application to use .Net framework 4.8 and FluentValidation Ver 10, but unfortunately, I couldn't continue because of an exception that I still cannot fix.
I have this customer class:
class Customer : MyClassBase
{
string _CustomerName = string.Empty;
public string CustomerName
{
get { return _CustomerName; }
set
{
if (_CustomerName == value)
return;
_CustomerName = value;
}
}
class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleFor(obj => obj.CustomerName).NotEmpty().WithMessage("{PropertyName} is Empty");
}
}
protected override IValidator GetValidator()
{
return new CustomerValidator();
}
}
This is my base class:
class MyClassBase
{
public MyClassBase()
{
_Validator = GetValidator();
Validate();
}
protected IValidator _Validator = null;
protected IEnumerable<ValidationFailure> _ValidationErrors = null;
protected virtual IValidator GetValidator()
{
return null;
}
public IEnumerable<ValidationFailure> ValidationErrors
{
get { return _ValidationErrors; }
set { }
}
public void Validate()
{
if (_Validator != null)
{
var context = new ValidationContext<Object>(_Validator);
var results = _Validator.Validate(context); **// <======= Exception is here in this line**
_ValidationErrors = results.Errors;
}
}
public virtual bool IsValid
{
get
{
if (_ValidationErrors != null && _ValidationErrors.Count() > 0)
return false;
else
return true;
}
}
}
When I run the application test I get the below exception:
System.InvalidOperationException HResult=0x80131509 Message=Cannot
validate instances of type 'CustomerValidator'. This validator can
only validate instances of type 'Customer'. Source=FluentValidation
StackTrace: at
FluentValidation.ValidationContext1.GetFromNonGenericContext(IValidationContext context) in C:\Projects\FluentValidation\src\FluentValidation\IValidationContext.cs:line 211 at FluentValidation.AbstractValidator1.FluentValidation.IValidator.Validate(IValidationContext
context)
Please, what is the issue here and How can I fix it?
Thank you
Your overall implementation isn't what I'd consider normal usage however the problem is that you're asking FV to validate the validator instance, rather than the customer instance:
var context = new ValidationContext<Object>(_Validator);
var results = _Validator.Validate(context);
It should start working if you change it to:
var context = new ValidationContext<object>(this);
var results = _Validator.Validate(context);
You're stuck with using the object argument for the validation context unless you introduce a generic argument to the base class, or create it using reflection.

Getting ActionContext of an action from another

Can I get an ActionContext or ActionDescriptor or something that can describe a specific action based on a route name ?
Having the following controller.
public class Ctrl : ControllerBase
{
[HttpGet]
public ActionResult Get() { ... }
[HttpGet("{id}", Name = "GetUser")]
public ActionResult Get(int id) { ... }
}
What I want to do is when "Get" is invoked, to be able to have access to "GetUser" metadata like verb, route parameters , etc
Something like
ActionContext/Description/Metadata info = somerService.Get(routeName : "GetUser")
or
ActionContext/Description/Metadata info = somerService["GetUser"];
something in this idea.
There is a nuget package, AspNetCore.RouteAnalyzer, that may provide what you want. It exposes strings for the HTTP verb, mvc area, path and invocation.
Internally it uses ActionDescriptorCollectionProvider to get at that information:
List<RouteInformation> ret = new List<RouteInformation>();
var routes = m_actionDescriptorCollectionProvider.ActionDescriptors.Items;
foreach (ActionDescriptor _e in routes)
{
RouteInformation info = new RouteInformation();
// Area
if (_e.RouteValues.ContainsKey("area"))
{
info.Area = _e.RouteValues["area"];
}
// Path and Invocation of Razor Pages
if (_e is PageActionDescriptor)
{
var e = _e as PageActionDescriptor;
info.Path = e.ViewEnginePath;
info.Invocation = e.RelativePath;
}
// Path of Route Attribute
if (_e.AttributeRouteInfo != null)
{
var e = _e;
info.Path = $"/{e.AttributeRouteInfo.Template}";
}
// Path and Invocation of Controller/Action
if (_e is ControllerActionDescriptor)
{
var e = _e as ControllerActionDescriptor;
if (info.Path == "")
{
info.Path = $"/{e.ControllerName}/{e.ActionName}";
}
info.Invocation = $"{e.ControllerName}Controller.{e.ActionName}";
}
// Extract HTTP Verb
if (_e.ActionConstraints != null && _e.ActionConstraints.Select(t => t.GetType()).Contains(typeof(HttpMethodActionConstraint)))
{
HttpMethodActionConstraint httpMethodAction =
_e.ActionConstraints.FirstOrDefault(a => a.GetType() == typeof(HttpMethodActionConstraint)) as HttpMethodActionConstraint;
if(httpMethodAction != null)
{
info.HttpMethod = string.Join(",", httpMethodAction.HttpMethods);
}
}
// Special controller path
if (info.Path == "/RouteAnalyzer_Main/ShowAllRoutes")
{
info.Path = RouteAnalyzerRouteBuilderExtensions.RouteAnalyzerUrlPath;
}
// Additional information of invocation
info.Invocation += $" ({_e.DisplayName})";
// Generating List
ret.Add(info);
}
// Result
return ret;
}
Try this:
// Initialize via constructor dependency injection
private readonly IActionDescriptorCollectionProvider _provider;
var info = _provider.ActionDescriptors.Items.Where(x => x.AttributeRouteInfo.Name == "GetUser");

NHinernate - Serializing Criteria to json and back. for a web interface usage

I have a web interface with a lot of data grids that allow the user passing a search criteria to the server.
I don't want to actually build an explicit method that will handle each grid individually, so I thought to allow passing JSON criteria from the client to the server.
This code
string res = JsonConvert.SerializeObject(Restrictions.Eq("id", "1"));
will return will return {"PropertyName":"id","Value":"1"} which is exactly what I wanted to pass from the client - perfect.
but this code with or statement:
string res = JsonConvert.SerializeObject(Restrictions.Or(Restrictions.Eq("id", "1"),Restrictions.Eq("id", "2")))
will return {}
What is the way to convert JSON into a Criteria and back in nHibernate.
To be more accurate, I have something like this in the client, and I want my server to support it. I can serialize the client ui to json.
Thanks
The empty json serialization happens because newtonsoft can only serialize public fields.
The code below will serialize all fields of a class regardless of its visibility.
public class MyContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => base.CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => base.CreateProperty(f, memberSerialization)))
.ToList();
props.ForEach(p => { p.Writable = true; p.Readable = true; });
return props;
}
}
...
var myOrs = Restrictions.Or(Restrictions.Eq("id", "1"), Restrictions.Eq("id", "2"));
var settings = new JsonSerializerSettings()
{
ContractResolver = new MyContractResolver()
};
string json = JsonConvert.SerializeObject(myOrs, settings);
Now to deserialize this json we have a problem, the NHibernate classes are either abstract or don't have a public empty constructor.
You will need to create a custom class to deserialize.
public class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Not implemented yet");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
if (obj.Value<string>("Op") == "or")
{
var leftJObject = obj["LeftHandSide"];
var rightJObject = obj["RightHandSide"];
var left = new SimpleExpression(leftJObject.Value<string>("PropertyName"), GetJTokenValue(leftJObject["Value"]), leftJObject.Value<string>("Op"));
var right = new SimpleExpression(rightJObject.Value<string>("PropertyName"), GetJTokenValue(rightJObject["Value"]), rightJObject.Value<string>("Op"));
return Restrictions.Or(left, right);
}
//TODO: The rest of the restrictions
return null;
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return true;
}
private object GetJTokenValue(JToken token)
{
if (token.Type == JTokenType.String)
{
return token.Value<string>();
}
else if (token.Type == JTokenType.Integer)
{
return token.Value<Int32>();
}
//TODO: The rest of the types
return null;
}
}
...
var result = JsonConvert.DeserializeObject<AbstractCriterion>(json, new MyConverter());

Custom serializer not working in Web API 2 for oData 4 when the URL contains $select

I implemented a custom serializer by inheriting ODataEntityTypeSerializer. The serializer sets the value of "MessageStateName" by getting the name of BayStateEnum from the value of "MessageState".
It works well only except when the URL contains "$select". I debugged the code and found it was executed and entityInstanceContext.EntityInstance had the correct value, but entityInstanceContext.EdmModel, which was of type System.Web.OData.Query.Expressions.SelectExpandBinder.SelectSome, still had an empty "MessageStateName".
public class CustomEntitySerializer : ODataEntityTypeSerializer
{
public CustomEntitySerializer(ODataSerializerProvider serializerProvider)
: base(serializerProvider)
{
}
public override ODataEntry CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext)
{
if (entityInstanceContext.EntityInstance is SmartLinkInfoModel)
{
var smartLinkInfo = entityInstanceContext.EntityInstance as SmartLinkInfoModel;
if (smartLinkInfo.ModemIMEI != null)
{
smartLinkInfo.ModemIMEIString = "0x" + string.Join(string.Empty, smartLinkInfo.ModemIMEI.Select(b => (b - 48).ToString()));
}
if (smartLinkInfo.SmartLinkHardwareId != null)
{
smartLinkInfo.SmartLinkHardwareIdString = "0x" + string.Join(string.Empty, smartLinkInfo.SmartLinkHardwareId.Select(b => b.ToString()));
}
if (smartLinkInfo.XbeeSourceId != null)
{
smartLinkInfo.XbeeSourceIdString = "0x" + string.Join(string.Empty, smartLinkInfo.XbeeSourceId.Select(b => b.ToString()));
}
}
else if (entityInstanceContext.EntityInstance is BayMessageModel)
{
var bayMessage = entityInstanceContext.EntityInstance as BayMessageModel;
bayMessage.MessageStateName = Enum.GetName(typeof(BayStateEnum), bayMessage.MessageState);
}
return base.CreateEntry(selectExpandNode, entityInstanceContext);
}
}
Your code to change the entityInstanceContext.EntityInstance is right, but it won't change the result of select, you can see
object propertyValue = entityInstanceContext.GetPropertyValue(structuralProperty.Name);
in ODataEntityTypeSerializer 's CreateStructuralProperty method, you should override this method, if the structuralProperty.Name is MessageStateName, then use (entityInstanceContext.EntityInstance as BayMessageModel).MessageStateName

Web API Help pages - customizing Property documentation

I have my web api and I added the web api help pages to auto-generate my documentation. It's working great for methods where my parameters are listed out, but I have a method like this:
public SessionResult PostLogin(CreateSessionCommand request)
And, on my help page, it is only listing the command parameter in the properties section. However, in the sample request section, it lists out all of the properties of my CreateSessionCommand class.
Parameters
Name | Description | Additional information
request | No documentation available. | Define this parameter in the request body.
I would like it instead to list all of the properties in my CreateSessionCommand class. Is there an easy way to do this?
So, I managed to devise a workaround for this problem, in case anyone is interested.
In HelpPageConfigurationExtensions.cs I added the following extension method:
public static void AlterApiDescription(this ApiDescription apiDescription, HttpConfiguration config)
{
var docProvider = config.Services.GetDocumentationProvider();
var addParams = new List<ApiParameterDescription>();
var removeParams = new List<ApiParameterDescription>();
foreach (var param in apiDescription.ParameterDescriptions)
{
var type = param.ParameterDescriptor.ParameterType;
//string is some special case that is not a primitive type
//also, compare by full name because the type returned does not seem to match the types generated by typeof
bool isPrimitive = type.IsPrimitive || String.Compare(type.FullName, typeof(string).FullName) == 0;
if (!isPrimitive)
{
var properties = from p in param.ParameterDescriptor.ParameterType.GetProperties()
let s = p.SetMethod
where s.IsPublic
select p;
foreach (var property in properties)
{
var documentation = docProvider.GetDocumentation(new System.Web.Http.Controllers.ReflectedHttpParameterDescriptor()
{
ActionDescriptor = param.ParameterDescriptor.ActionDescriptor,
ParameterInfo = new CustomParameterInfo(property)
});
addParams.Add(new ApiParameterDescription()
{
Documentation = documentation,
Name = property.Name,
Source = ApiParameterSource.FromBody,
ParameterDescriptor = param.ParameterDescriptor
});
}
//since this is a complex type, select it to be removed from the api description
removeParams.Add(param);
}
}
//add in our new items
foreach (var item in addParams)
{
apiDescription.ParameterDescriptions.Add(item);
}
//remove the complex types
foreach (var item in removeParams)
{
apiDescription.ParameterDescriptions.Remove(item);
}
}
And here is the Parameter info instanced class I use
internal class CustomParameterInfo : ParameterInfo
{
public CustomParameterInfo(PropertyInfo prop)
{
base.NameImpl = prop.Name;
}
}
Then, we call the extension in another method inside the extensions class
public static HelpPageApiModel GetHelpPageApiModel(this HttpConfiguration config, string apiDescriptionId)
{
object model;
string modelId = ApiModelPrefix + apiDescriptionId;
if (!config.Properties.TryGetValue(modelId, out model))
{
Collection<ApiDescription> apiDescriptions = config.Services.GetApiExplorer().ApiDescriptions;
ApiDescription apiDescription = apiDescriptions.FirstOrDefault(api => String.Equals(api.GetFriendlyId(), apiDescriptionId, StringComparison.OrdinalIgnoreCase));
if (apiDescription != null)
{
apiDescription.AlterApiDescription(config);
HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator();
model = GenerateApiModel(apiDescription, sampleGenerator);
config.Properties.TryAdd(modelId, model);
}
}
return (HelpPageApiModel)model;
}
The comments that are used for this must be added to the controller method and not the properties of the class object. This might be because my object is part of an outside library
this should go as an addition to #Josh answer. If you want not only to list properties from the model class, but also include documentation for each property, Areas/HelpPage/XmlDocumentationProvider.cs file should be modified as follows:
public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
{
ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor;
if (reflectedParameterDescriptor != null)
{
if (reflectedParameterDescriptor.ParameterInfo is CustomParameterInfo)
{
const string PropertyExpression = "/doc/members/member[#name='P:{0}']";
var pi = (CustomParameterInfo) reflectedParameterDescriptor.ParameterInfo;
string selectExpression = String.Format(CultureInfo.InvariantCulture, PropertyExpression, pi.Prop.DeclaringType.FullName + "." + pi.Prop.Name);
XPathNavigator methodNode = _documentNavigator.SelectSingleNode(selectExpression);
if (methodNode != null)
{
return methodNode.Value.Trim();
}
}
else
{
XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor);
if (methodNode != null)
{
string parameterName = reflectedParameterDescriptor.ParameterInfo.Name;
XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName));
if (parameterNode != null)
{
return parameterNode.Value.Trim();
}
}
}
}
return null;
}
and CustomParameterInfo class should keep property info as well:
internal class CustomParameterInfo : ParameterInfo
{
public PropertyInfo Prop { get; private set; }
public CustomParameterInfo(PropertyInfo prop)
{
Prop = prop;
base.NameImpl = prop.Name;
}
}
This is currently not supported out of the box. Following bug is kind of related to that:
http://aspnetwebstack.codeplex.com/workitem/877