ASP.NET core JSON model binding int to enum - enums

Scenario
I want to map an incoming JSON request which has integer values to corresponding Enum values.
The code below is simplified for the question in reality there are much more fields on MyRequest class.
Data Code
public enum Policy
{
Unknown = 0,
Policy1 = 1,
Anticipated = 2
}
public enum Design
{
Unknown = 0,
Project = 1,
Days = 2
}
public class Policies
{
public Policy? PolicyId { get; set; }
public Design? DesignId { get; set; }
}
class MyRequest
{
public int Id { get; set; }
public Policies Policies { get; set; }
}
JSON Request
{
"policies": {
"policyId": 2,
"designId": 2
},
"id": 1
}
Controller Code
[HttpPut]
public async Task<IActionResult> Put(MyRequest request)
{
// reques.Policies.DesignId is null
// reques.Policies.PolicyId is null
}

After digging deeper into the problem and with almost no docs by Microsoft I found this similar question that gave me the right direction: implement a custom JSONConverter.
JSONConverter
public class ContractConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Policies) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var rawValue = serializer.Deserialize<dynamic>(reader);
var policyId = (Policy)(rawValue.policyId);
var designId = (Design)(rawValue.designId);
return new Policies(policyId, designId);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanWrite => false;
}
Startup.cs
services.AddControllers()
.AddNewtonsoftJson(o=> o.SerializerSettings.Converters.Add(new ContractConverter()));

Related

Asp.NET core 3 custom model attribute error handling

I need to catch the exception form my custom model attribute on validating it.
Here is my HttpStatusCodeExceptionMiddleware:
public class HttpStatusCodeExceptionMiddleware
{
private readonly RequestDelegate _next;
public HttpStatusCodeExceptionMiddleware(RequestDelegate next)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (HttpStatusCodeException ex)
{
if (context.Response.HasStarted)
{
throw;
}
context.Response.Clear(); //<-possible Angular CORS error
context.Response.StatusCode = ex.StatusCode;
context.Response.ContentType = ex.ContentType;
ProblemDetails responseBody = new ProblemDetails(ex.Message, ex.StatusCode, "Request error", ex.Key);
await context.Response.WriteAsync(JsonConvert.SerializeObject(responseBody));
return;
}
}
public class HttpStatusCodeException : Exception
{
public int StatusCode { get; set; }
public string ContentType { get; set; } = #"text/plain";
//key for translation
public string Key { get; set; }
public HttpStatusCodeException(HttpResponseType statusCode)
{
this.StatusCode = (int)statusCode;
}
public HttpStatusCodeException(HttpResponseType statusCode, string message, string key) : base(message)
{
StatusCode = (int)statusCode;
Key = key;
}
public HttpStatusCodeException(HttpResponseType statusCode, Exception inner, string key) : base(inner.ToString())
{
Key = key;
}
public HttpStatusCodeException(HttpResponseType statusCode, JObject errorObject, string key) : this(statusCode, errorObject.ToString(), key)
{
this.ContentType = #"application/json";
}
}
public static class HttpStatusCodeExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseHttpStatusCodeExceptionMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<HttpStatusCodeExceptionMiddleware>();
}
}
And I am using it in the Startup.cs Configure method like this:
app.UseHttpStatusCodeExceptionMiddleware();
But in this scenario I need to catch the model attribute validation exception, but my solution only catches the controller exceptions.
Is there a way to do it?
thnx
You need to throw a HttpStatusCodeException then you could hit the catch (HttpStatusCodeException ex):
1.Custom validation attribute:
public class TestNameAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value.ToString().StartsWith("a"))
{
throw new HttpStatusCodeException(HttpStatusCode.BadRequest, "Name could not start with a", value.ToString());
//return new ValidationResult("Name could not start with a");
}
return ValidationResult.Success;
}
}
2.Model:
public class YourModel
{
public long Id { get; set; }
[TestName]
public string Name { get; set; }
public string DisplayName { get; set; }
}
3.Test action:
[HttpPost]
public async Task<ActionResult<Mood>> Post(YourModel model)
{
_context.YourModel.Add(model);
await _context.SaveChangesAsync();
return CreatedAtAction("Get", new { id = model.Id }, model);
}

How to Change the Response of the API in OnActionExecuted method of Controller in .Net Core

Json response:
{
"ID": 1,
"Value": 10,
"User": null
}
I need to change the User value in each response in OnActionExecuted method.
public override void OnActionExecuted(ActionExecutedContext context)
{
var response = context.Result;
}
But unable to read the Result and Update details.
You just need to do some casting, I think. This code needs some error handling but otherwise demonstrates what you want:
public class ViewModel
{
public int ID { get; set; }
public int Value { get; set; }
public string User { get; set; }
}
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IActionResult Get()
{
var vm = new ViewModel()
{
ID = 1,
Value = 10
};
return Ok(vm);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
var result = context.Result as OkObjectResult;
var vm = result.Value as ViewModel;
vm.User = "ardalis";
}
}
The result:
{"id":1,"value":10,"user":"ardalis"}

Web API Parameter Binding

Request Payload does not get coverted to Custom Request Object.
payload
appl5=MC~IC&i~PhoneToPhone~inet_ptt_cb_phn~1=440&inet_ptt_cb_phn~3=7406&i~PhoneToPhone~inet_ptt_cb_delay=0&BeenHere=TRUE
It has ~ in keyvalue pair (both in key and value).
I have a Request Model that convert the input params to avalid Object.
Note: I cannot have ~ in my C# property. ( Can i ? )
My Post method has the following code
public HttpResponseMessage Post(ClientRequest request)
{
HttpResponseMessage response;
try
{
ProcessRequest target = new ProcessRequest(myRepository, myService);
response = target.Process(request);
}
catch (Exception exception)
{
response = Request.CreateErrorResponse(HttpStatusCode.NotFound, exception.Message);
//TODO : Log Exception.
}
return response;
}
Model
public class ClientRequest
{
public string Appl5 { get; set; }
public string I_PhoneToPhone_inet_ptt_cb_phn_1 { get; set; }
public string I_PhoneToPhone_inet_ptt_cb_delay { get; set; }
public string Inet_ptt_cb_phn_3 { get; set; }
public string BeenHere { get; set; }
}
My request object does not have the values for i~PhoneToPhone~inet_ptt_cb_phn~1, its null.
My understanding was the model binding is not happening because the payload key does not match
with my model (ClientRequest) which does not have ~ for i~PhoneToPhone~inet_ptt_cb_phn~1
in stead i have i_PhoneToPhone_inet_ptt_cb_phn_1
Should i use Custom Binding ?
At last, Added Custom Model binder
public class PostParameterModelBinder : IModelBinder
{
bool IModelBinder.BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
bool success = false;
if (bindingContext.ModelType == typeof(ClientRequest))
{
NameValueCollection postData = null;
postData = actionContext.Request.Content.ReadAsFormDataAsync().Result;
ClientRequest clientrequest = MapPostDataToRequest(postData);
bindingContext.Model = clientrequest;
success = true;
}
return success;
}
}
}

How to model bind a class that implements an interface?

The model binding worked fine until i implemented interfaces on top of the following classes:
public class QuestionAnswer : IQuestionAnswer
{
public Int32 Row_ID { get; set; }
public Int32 Column_ID { get; set; }
public String Value { get; set; }
}
public class HiddenAnswer : IHiddenAnswer
{
public Int32 Hidden_Field_ID { get; set; }
public String Hidden_Field_Value { get; set; }
}
public class SurveyAnswer : ISurveyAnswer
{
public string SessionID { get; set; }
public List<IQuestionAnswer> QuestionAnswerList { get; set; }
public List<IHiddenAnswer> HiddenAnswerList { get; set; }
public SurveyAnswer()
{
QuestionAnswerList = new List<IQuestionAnswer>();
HiddenAnswerList = new List<IHiddenAnswer>();
}
}
Now that the interfaces are there, i get a 500 (Internal Server Error)
The javascript that i use to model bind is the following:
$('#submitbutton').click(function () {
var answers = new Array();
var hiddenfields = new Array();
var formname = "#" + $("#formname").val();
$(':input', formname).each(function () {
if ($(this).is(":text") || $(this).is(":radio") || $(this).is(":checkbox"))
{
var answerObject = {
Column_ID: $(this).attr('data-column_id'),
Row_ID: $(this).attr('data-row_id'),
Value: $(this).attr('data-theValue')
};
answers.push(answerObject);
}
else if($(this).is(":hidden")) {
var hiddenObject =
{
Hidden_Field_ID: $(this).attr('data-hidden_field_id'),
Hidden_Field_Value: $(this).attr('data-hidden_field_value')
}
hiddenfields.push(hiddenObject);
}
});
$('textarea', formname).each(function () {
var answerObject = {
Column_ID: $(this).attr('data-column_id'),
Row_ID: $(this).attr('data-row_id'),
Value: $(this).val(),
};
answers.push(answerObject);
});
var allAnswers = {
SessionID: 0,
QuestionAnswerList: answers,
HiddenAnswerList: hiddenfields
}
postForm(allAnswers);
});
The Controller Action looks like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult SubmitSurvey(SurveyAnswer answers)
{
// Dette tillader CORS
Response.AppendHeader("Access-Control-Allow-Origin", "*");
bc.SaveSurvey(answers);
return null;
}
what am i doing wrong?
what am i doing wrong?
You cannot expect the model binder to know that when it encounters the IQuestionAnswer interface on your SurveyAnswer view model it should use the QuestionAnswer type. It's nice that you have declared this implementation of the interface but the model binder has no clue about it.
So you will have to write a custom model binder for the IQuestionAnswer interface (same for the IHiddenAnswer interface) and indicate which implementation do you want to be used:
public class QuestionAnswerModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var type = typeof(QuestionAnswer);
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
which will be registered in your Application_Start:
ModelBinders.Binders.Add(typeof(IQuestionAnswer), new QuestionAnswerModelBinder());

Concrete implementation of generic base class and extension method

The end goal for this post is to override the ToString() method of a concrete implementation of a generic base class while still being able to search the implementation using Linq flattening technique. So if you read this and see a better way let me know. I'm using Telerik controls for Silverlight and they won't change their api to allow some of their control properties to be data-bound and instead rely on the ToString() method of whatever object they are bound to. yea, stupid.. Anyway here is what I've got.
RadTreeView control on my page. The FullPath property of each node in the treeview uses the ToString() method of each item its bound to (so this is what I need to override).
I had to create an "intermediary" class to enhance my base model class so it can be bound as a heirarchy in the tree view and then a concrete implementation of that generic class to override ToString(). Now the problem is I have a Linq extension that explodes because it cannot convert the concrete implementation back to the base generic class. I love generics but this is too much for me. Need help on solving the extension method issue.
Intermediary generic class:
public class HeirarchicalItem<T> : NotifyPropertyChangedBase, INotifyCollectionChanged where T : class
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs ea)
{
if (CollectionChanged != null)
CollectionChanged(this, ea);
}
public HeirarchicalItem() { }
public HeirarchicalItem(T item)
{
Item = item;
}
public HeirarchicalItem(IEnumerable<T> collection)
{
CopyFrom(collection);
}
private T _item;
public T Item
{
get
{
return _item;
}
set
{
_item = value;
RaisePropertyChanged<HeirarchicalItem<T>>(a => a.Item);
}
}
private ObservableCollection<HeirarchicalItem<T>> _children = new ObservableCollection<HeirarchicalItem<T>>();
public virtual ObservableCollection<HeirarchicalItem<T>> Children
{
get { return _children; }
set
{
_children = value;
RaisePropertyChanged<HeirarchicalItem<T>>(a => a.Children);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private void CopyFrom(IEnumerable<T> collection)
{
if ((collection != null))
{
using (IEnumerator<T> enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
HeirarchicalItem<T> newHeirarchicalItem = new HeirarchicalItem<T>(enumerator.Current);
Children.Add(newHeirarchicalItem);
RaisePropertyChanged<HeirarchicalItem<T>>(a => a.Children);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
}
}
}
}
}
Base model class: (data is shuttled to and from WCF Ria service using this class)
public class tbl_Path : EntityBase, IFullPath, IEquatable<tbl_Path>, IEqualityComparer<tbl_Path>
{
public tbl_Path();
public int GetHashCode(tbl_Path obj);
public override string ToString();
public DateTime CreateDate { get; set; }
public short Depth { get; set; }
public string FullPath { get; set; }
public bool IsAuthorized { get; set; }
public bool IsSelected { get; set; }
public string Name { get; set; }
public override IEnumerable<Operation> Operations { get; }
public int? ParentPathID { get; set; }
public int PathID { get; set; }
public Guid SecurityKey { get; set; }
public EntityCollection<tbl_Configuration> tbl_Configuration { get; set; }
public EntityCollection<tbl_Key> tbl_Key { get; set; }
public EntityCollection<tbl_SecurityACL> tbl_SecurityACL { get; set; }
public EntityCollection<tbl_SecurityInheriting> tbl_SecurityInheriting { get; set; }
public EntityCollection<tbl_Variable> tbl_Variable { get; set; }
}
Concrete Implementation so that I can override ToString():
public class HeirarchicalPath : HeirarchicalItem<tbl_Path>
{
public HeirarchicalPath()
{
}
public HeirarchicalPath(tbl_Path item)
: base(item)
{
}
public HeirarchicalPath(IEnumerable<tbl_Path> collection)
: base(collection)
{
}
public override string ToString()
{
return Item.Name; **// we override here so Telerik is happy**
}
}
And finally here is the Linq extension method that explodes during compile time because I introduced a concrete implementation of my generic base class.
public static IEnumerable<T> Traverse<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> fnRecurse)
{
foreach (T item in source)
{
yield return item;
IEnumerable<T> seqRecurse = fnRecurse(item);
if (seqRecurse != null)
{
foreach (T itemRecurse in Traverse(seqRecurse, fnRecurse))
{
yield return itemRecurse;
}
}
}
}
Actual code that is breaking: (x.Children is highlighted with the error)
Cannot implicitly convert type
'System.Collections.ObjectModel.ObservableCollection<HeirarchicalItem<tbl_Path>>' to
'System.Collections.Generic.IEnumerable<HeirarchicalPath>'. An explicit conversion
exists (are you missing a cast?)
HeirarchicalPath currentItem = this.Paths.Traverse(x => x.Children).Where(x => x.Item.FullPath == "$/MyFolder/Hello").FirstOrDefault();
Figured it out. Been working on this all day and minutes after posting the question I resolve it as always.
Just needed to add this bit to my concrete implementation and no more compiler errors.
private ObservableCollection<HeirarchicalPath> _children = new ObservableCollection<HeirarchicalPath>();
public new ObservableCollection<HeirarchicalPath> Children
{
get
{
return _children;
}
set
{
if (value == null)
return;
_children = value;
RaisePropertyChanged<HeirarchicalPath>(a => a.Children);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}

Resources