How to handle a POST method that also has a parameter of unknown type? - asp.net-mvc

I am coding to handle a Facebook callback. Facebook calls the same Url with POST but with different object types. I am not ready to handle all the parameter types, so I tried the following:
public void Post([FromBody]class1 webhook1)
{ // for object1
}
public void Post([FromBody]class2 webhook2)
{ // for object12
}
public async Task Post()
{
string rawData = await Request.Content.ReadAsStringAsync();
log(rawData);
}
However, I get this exception:
"Multiple actions were found that match the request ...
Is there a way to allow a fallback option for parameter types I am not ready to handle?

Method overloading based on the type of parameter is not supported for "web" routing methods
Use route attribute:
[Route("webhook1")]
public void Post([FromBody]class1 webhook1) {}
[Route("webhook2")]
public void Post([FromBody]class2 webhook2) {}
Or if you don't want client side to know about different parameter's types,
then make one "web" method where you read raw data from the body of request,
detect a type of data and call correspondent method to handle it

You may want to use attribute based routing instead as this gives you a lot more flexibility. Web api donot support standard routing methods well.
It should be something like this:
[RoutePrefix("api/example")]
public ExampleController : ApiController{
[Route("postwebhook1")]
public void Post([FromBody]class1 webhook1)
{ // for object1
}
[Route("postwebhook2")]
public void Post([FromBody]class2 webhook2)
{ // for object12
}
[Route("post")]
public async Task Post()
{
string rawData = await Request.Content.ReadAsStringAsync();
log(rawData);
}
}

Related

How to perform unit testing on a void method in web api controller

Hi I have a update method in webAPI and that is a void method and I want to perform unit testing on that method.How do I do that??
Not Found any solution.
Below is webapi controller method :-
[HttpPut]
public void UpdatePushNotification(PushNotificationQueueBO pushnotificationqueueBO)
{
PushNotificationQueueBO.UpdatePushNotificationQueue(pushnotificationqueueBO);
}
Below is the unit test case for above method
[TestMethod]
public void UpdatePushNotificationQueue_ShouldUpdate()
{
var item = GetDemoPushNotificationQueue();
var controller = new PushNotificationQueueController();
var result = controller.UpdatePushNotification(item) as ;
Assert.IsNotNull(result);
}
I want what do I write after as in
var result = controller.UpdatePushNotification(item) as ???
The controller method is void so there is no return type and nothing to cast it to.
I believe this to be an XY problem.
void controller actions will always return HTTP Status Code 200 OK at run-time except when an exception is thrown.
Based on the tags in original post the assumption is that the mentioned controller is an ApiController
which means that the controller can be refactored to
[HttpPut]
public IHttpActionResult UpdatePushNotification(PushNotificationQueueBO pushnotificationqueueBO) {
PushNotificationQueueBO.UpdatePushNotificationQueue(pushnotificationqueueBO);
return Ok();
}
There is also the option to wrap it in a try-catch in case of errors
[HttpPut]
public IHttpActionResult UpdatePushNotification(PushNotificationQueueBO pushnotificationqueueBO) {
try {
PushNotificationQueueBO.UpdatePushNotificationQueue(pushnotificationqueueBO);
return Ok();
} catch (Exception ex) {
return InternalServerError(ex);
//OR
//return InternalServerError()
}
}
But that is more of a cross-cutting concern that can be handled by action filters.
This would then allow for an actual return type to be asserted
//...omitted for brevity
IHttpActionResult result = controller.UpdatePushNotification(item);
Assert.IsNotNull(result);
The PushNotificationQueueBO business object however, appears to be making a static member call.
PushNotificationQueueBO.UpdatePushNotificationQueue(pushnotificationqueueBO);
This makes it difficult to unit test the encapsulated API method call in isolation and may result in undesired behavior.
It is suggested that the static business object call be encapsulated behind an abstraction and implementation that can be replaced by a mock when testing in isolation.
You can test a void function in different ways and it depends on what the void method does. For example, if a void method increments the numeric value of a property of its class, then you can use that property in your test. In your case, your void method performs this action;
PushNotificationQueueBO.UpdatePushNotificationQueue(pushnotificationqueueBO);
Firstly, identify what this action does and what it affects. For example, if this method's action performs a manipulation on a queue object, then you can test this object as a result of the void method.

MVC optional body parameter

I am trying to wire up a webhook from a 3rd party system.
When creating the subscription it hits the URL i provide and requires a validated token returned to create the hook.
When the event is triggered the hook posts to the same URL i provided with data in the body.
How can I get a Core 2.1 MVC controller/routing to see these as either two different methods on the controller or a method signature where the complex object is optional?
Either two POST methods (this creates ambiguity exception)
public async Task<IActionResult> Index(){}
public async Task<IActionResult> Index([FromBody] ComplexObject co){}
or complexObject is optional (if not it throws a Executing ObjectResult, writing value of type '"Microsoft.AspNetCore.Mvc.SerializableError" on the subscription creation step.)
public async Task<IActionResult> Index([FromBody] ComplexObject co){}
Another way around this :
public class AllowBindToNullAttribute : ModelBinderAttribute
{
public AllowBindToNullAttribute()
: base(typeof(AllowBindToNullBinder))
{
}
public class AllowBindToNullBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var stream = bindingContext.HttpContext.Request.Body;
string body;
using (var reader = new StreamReader(stream))
{
body = await reader.ReadToEndAsync();
}
var instance = JsonConvert.DeserializeObject(body, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(instance);
}
}
}
You'd use it like this:
public async Task<IActionResult> Index(
[FromBody] [AllowBindToNull] ComplexObject co = null){}
I used the empty parameter method signature and checked the body for data. Not ideal.

How can I get the raw JSON dictionary from ASP.NET MVC Web API post?

With a ApiController subclass, it has the ability in Post method to bind it to an existing model object such as
public class RegisterController : ApiController
{
public void Post(Product product)
but if the incoming JSON data contains data that I'll use to create multiple model objects, how can I get to the data directly?
public void Post(dynamic value)
returns value as null. Is there an easy shorthand way of getting to it like request.POST['name'] or something?
Let's say the data looks like
{
'productID':1,
'productName':'hello',
'manufacturerID':1,
'manufacturerName':'world'
}
One option may be using one of the ReadAsAsync* methods in HttpContent instance off of the Request object
public void Post() {
var result = this.Request.Content.ReadAsAsync<string>().Result;
}
I don't know what format you're sending your data in, but you can retrieve it this way.
You could try this too for multiple objects...
public void Post(IEnumberable<Product> products) {
}

Access the error message in ModelState error dictionary in ASP.net MVC unit test

I have added a key-value pair in the action result like this:
[HttpPost, Authorize]
public ActionResult ListFacilities(int countryid)
{
...
ModelState.AddModelError("Error","No facilities reported in this country!");
...
}
I have some cumbersome codes like these in a unit test to :
public void ShowFailforFacilities()
{
//bogus data
var facilities = controller.ListFacilities(1) as PartialViewResult;
Assert.AreSame("No facilities reported in this country!",
facilities.ViewData.ModelState["Error"].Errors.FirstOrDefault().ErrorMessage);
}
Of course, it works whenever I have only one error.
I don't like facilities.ViewData.ModelState["Error"].Errors.FirstOrDefault().ErrorMessage.
Is there an easier way for me to fetch the value from that dictionary?
Your FirstOrDefault isn't needed, because you'll get a NullReferenceException when accessing ErrorMessage. You can just use First().
Either way, I couldn't find any built-in solution. What I've done instead is create an extension method:
public static class ExtMethod
{
public static string GetErrorMessageForKey(this ModelStateDictionary dictionary, string key)
{
return dictionary[key].Errors.First().ErrorMessage;
}
}
Which works like this:
ModelState.GetErrorMessageForKey("error");
If you need better exception handling, or support for multiple errors, its easy to extend...
If you want this to be shorter you can create an extension method for the ViewData...
public static class ExtMethod
{
public static string GetModelStateError(this ViewDataDictionary viewData, string key)
{
return viewData.ModelState[key].Errors.First().ErrorMessage;
}
}
and usage:
ViewData.GetModelStateError("error");
Have you tried this?
// Note: In this example, "Error" is the name of your model property.
facilities.ViewData.ModelState["Error"].Value
facilities.ViewData.ModelState["Error"].Error

Questions regarding HttpContext, HttpContextBase, and Action Filters

I'm try to build a static property on a static class that will basically return a cookie value, to be used across my MVC site (MVC 3, if it matters). Something like this:
public static class SharedData
{
public static string SomeValue
{
get
{
if (HttpContext.Current.Request.Cookies["SomeValue"] == null)
{
CreateNewSomeValue();
}
return HttpContext.Current.Request.Cookies["SomeValue"].Value.ToString();
}
}
}
I need to access this from within controller actions, global.asax methods, and action filters. But the problem is, when action filters run, HttpContext is not available. Right now, I have to have a separate static method just to pull the cookie from the filter context that I pass in, which seems awkward.
What is the best solution for building such a static method for retrieving a cookie value like this that works from both controller actions and action filters? Or is there a better approach for doing something like this?
Thanks in advance.
The call to the static HttpContext.Current is not good design. Instead, create an extension method to access the cookie from an instance of HttpContext and HttpContextBase.
I wrote a little helper for you. You can use it to perform your functionality from within an action filter.
public static class CookieHelper
{
private const string SomeValue = "SomeValue";
public static string get_SomeValue(this HttpContextBase httpContext)
{
if(httpContext.Request.Cookies[SomeValue]==null)
{
string value = CreateNewSomeValue();
httpContext.set_SomeValue(value);
return value;
}
return httpContext.Request.Cookies[SomeValue].Value;
}
public static void set_SomeValue(this HttpContextBase httpContext, string value)
{
var someValueCookie = new HttpCookie(SomeValue, value);
if (httpContext.Request.Cookies.AllKeys.Contains(SR.session))
{
httpContext.Response.Cookies.Set(someValueCookie);
}
else
{
httpContext.Response.Cookies.Add(someValueCookie);
}
}
}
Note: You could easily make these methods work on HttpContext instead just by replacing the HttpContextBase parameter with HttpContext.
As JohnnyO pointed out above, I had access to HttpContext from within my action filter all along. At least, in the particular action filter method where this was needed. There may have been some other filter/method that did not have access at one point, but for now, this is working as I need it to.

Resources