I am trying to use both FromUri and FromBody in web api 2 to populate an incoming request model. I understand I need to write a custom model binder to do that. Here is the example everyone references. This solution has been incorporated into the WebAPIContrib nuGet pacakge whose source code can be seen here on github.
I'm having trouble getting the MvcActionValueBinder to work with application/json body content. Here is part of the source that is throwing the exception.
class MvcActionBinding : HttpActionBinding
{
// Read the body upfront , add as a ValueProvider
public override Task ExecuteBindingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
HttpRequestMessage request = actionContext.ControllerContext.Request;
HttpContent content = request.Content;
if (content != null)
{
FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;
if (fd != null)
{
IValueProvider vp = new NameValuePairsValueProvider(fd, CultureInfo.InvariantCulture);
request.Properties.Add(Key, vp);
}
}
return base.ExecuteBindingAsync(actionContext, cancellationToken);
}
}
This line is throwing the exception:
FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;
Here is the exception:
System.AggregateException
{"Cannot deserialize the current JSON object (e.g.
{\"name\":\"value\"}) into type
'System.Net.Http.Formatting.FormDataCollection' because the type
requires a JSON array (e.g. [1,2,3]) to deserialize correctly.\r\nTo
fix this error either change the JSON to a JSON array (e.g. [1,2,3])
or change the deserialized type so that it is a normal .NET type (e.g.
not a primitive type like integer, not a collection type like an array
or List) that can be deserialized from a JSON object.
JsonObjectAttribute can also be added to the type to force it to
deserialize from a JSON object.\r\nPath 'creditLimit', line 2,
position 17."}
How can I get the model binder to work with applciation/json content instead of x-www-form-urlencoded content? Here is a similar question with no answer on the asp.net forums.
Update:
Here is the controller method:
[Route("{accountId:int}/creditlimit")]
[HttpPut]
public async Task<IHttpActionResult> UpdateAccountCreditLimit(int accountId, [FromBody] RequestObject request)
{
// omitted for brevity
}
Here is the RequestObject:
class RequestObject
{
public int AccountId { get; set; }
public decimal CreditLimit { get; set; }
}
Here is the postman endpoint to test, its a PUT:
http://localhost/api/accounts/47358/creditlimit
The body I have set to application/json. Here is sample content.
{ "creditLimit": 125000.00 }
And yes, I realize I could change the controller method to do all FromUri or all FromBody instead. I do not have the liberty of doing that. Thanks.
I had the same issue and I think I finally figure this out.
Here is the code :
internal sealed class MvcActionValueBinder : DefaultActionValueBinder
{
private static readonly Type stringType = typeof(string);
// Per-request storage, uses the Request.Properties bag. We need a unique key into the bag.
private const string Key = "5DC187FB-BFA0-462A-AB93-9E8036871EC8";
private readonly JsonSerializerSettings serializerSettings;
public MvcActionValueBinder(JsonSerializerSettings serializerSettings)
{
this.serializerSettings = serializerSettings;
}
public override HttpActionBinding GetBinding(HttpActionDescriptor actionDescriptor)
{
var actionBinding = new MvcActionBinding(serializerSettings);
HttpParameterDescriptor[] parameters = actionDescriptor.GetParameters().ToArray();
HttpParameterBinding[] binders = Array.ConvertAll(parameters, DetermineBinding);
actionBinding.ParameterBindings = binders;
return actionBinding;
}
private HttpParameterBinding DetermineBinding(HttpParameterDescriptor parameter)
{
HttpConfiguration config = parameter.Configuration;
var attr = new ModelBinderAttribute(); // use default settings
ModelBinderProvider provider = attr.GetModelBinderProvider(config);
IModelBinder binder = provider.GetBinder(config, parameter.ParameterType);
// Alternatively, we could put this ValueProviderFactory in the global config.
var valueProviderFactories = new List<ValueProviderFactory>(attr.GetValueProviderFactories(config)) { new BodyValueProviderFactory() };
return new ModelBinderParameterBinding(parameter, binder, valueProviderFactories);
}
// Derive from ActionBinding so that we have a chance to read the body once and then share that with all the parameters.
private class MvcActionBinding : HttpActionBinding
{
private readonly JsonSerializerSettings serializerSettings;
public MvcActionBinding(JsonSerializerSettings serializerSettings)
{
this.serializerSettings = serializerSettings;
}
// Read the body upfront, add as a ValueProvider
public override Task ExecuteBindingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
HttpRequestMessage request = actionContext.ControllerContext.Request;
HttpContent content = request.Content;
if (content != null)
{
string result = request.Content.ReadAsStringAsync().Result;
if (!string.IsNullOrEmpty(result))
{
var jsonContent = JObject.Parse(result);
var values = new Dictionary<string, object>();
foreach (HttpParameterDescriptor parameterDescriptor in actionContext.ActionDescriptor.GetParameters())
{
object parameterValue = GetParameterValue(jsonContent, parameterDescriptor);
values.Add(parameterDescriptor.ParameterName, parameterValue);
}
IValueProvider valueProvider = new NameValuePairsValueProvider(values, CultureInfo.InvariantCulture);
request.Properties.Add(Key, valueProvider);
}
}
return base.ExecuteBindingAsync(actionContext, cancellationToken);
}
private object GetParameterValue(JObject jsonContent, HttpParameterDescriptor parameterDescriptor)
{
string propertyValue = jsonContent.Property(parameterDescriptor.ParameterName)?.Value.ToString();
if (IsSimpleParameter(parameterDescriptor))
{
// No deserialization needed for value type, a cast is enough
return Convert.ChangeType(propertyValue, parameterDescriptor.ParameterType);
}
return JsonConvert.DeserializeObject(propertyValue, parameterDescriptor.ParameterType, serializerSettings);
}
private bool IsSimpleParameter(HttpParameterDescriptor parameterDescriptor)
{
return parameterDescriptor.ParameterType.IsValueType || parameterDescriptor.ParameterType == stringType;
}
}
// Get a value provider over the body. This can be shared by all parameters.
// This gets the values computed in MvcActionBinding.
private class BodyValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
actionContext.Request.Properties.TryGetValue(Key, out object vp);
return (IValueProvider)vp; // can be null
}
}
}
To explain, the trick is to first read the request content as a string and then loading it into a JObject.
For each parameter present in actionContext.ActionDescriptor a dictionnary is populated with the parameter name as key and we use the parameter type to add the object value.
Depending on the parameter type we either do a simple cast or use Json.NET to deserialize the value into the desired type.
Please note you may need to add special case for value type to manage for example enumerations or Guid.
In my example, I pass around a JsonSerializerSettings because I have some custom converters that I want to use, be you may not need it.
You should be able to achieve this with the default model binding functionality in Web API 2 itself.
First thing you need to do is pass the data as JSON string as following.
data: JSON.stringify({ "creditLimit": 125000.00 })
The accountId will be read from the URL and the default JsonFormatter of the Web API 2 will try to bind your second parameter request from the body. It will find the creditLimit and will create an instance of RequestObject with the creditLimit populated.
You can then, inside the controller, assign the accountId value to the RequestObject other property. That way you don't need to pass the accountId as part of your request body. You only pass that as part of your URL endpoint.
The following link is a good resource for more in-depth detail.
http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
Related
I have an API request that goes off and the response structure back looks like this:
{
"MessageBody": {
"foo" : ""
}
}
The properties under MessageBody can be anything, not only foo, but its value is always a string.
eg. {"MessageBody": { "Token": "abc" }} or {"MessageBody": { "Name": "abc" }}
How can I capture this response from the API as a generic object for the property under MessageBody?
I can represent the first example above as:
public class MessageBody
{
public string Token { get; set; }
}
How would I represent both Token or Name properties under the same MessageBody object? There's a bunch of different values that MessageBody can have, but again they would all be of type string.
I have acheived something similar using Newtonsoft
Your route should take the body in as a generic object and it can then be deserialized into any object you'd like:
/*Using this method, the controller will automatically handle
validating proper Json format*/
[HttpPost]
public async Task<IActionResult> Post([FromBody] object Body)
{
/*here you will send the generic object to a service which will deserialize.
the object into an expected model.*/
customService.HandlePost(Body);
}
Now create an object with any expected fields you would get from the body. (Json2csharp.com is extremely useful!)
public class MessageBody
{
public string Token { get; set; }
public string Name { get; set; }
}
Inside your service you can handle the object like this:
using Newtonsoft.Json
using Models.MessageBody
public class customService()
{
public void HandlePost(object body)
{
var DeserializedBody = JsonConvert.DeserializeObject<MessageBody>(body);
//Any Values that were not assigned will be null in the deserialized object
if(DeserializedBody.Name !== null)
{
//do something
}
}
}
This is obviously a very bare bones implementation, error handling will be important to catch any invalid data. Instead of using one object and null fields to get the data you need, I would recommend adding a "subject" route variable (string) that you can use to determine which object to deserialize the body into.
post *api/MessageBody/{Subject}
I am transforming HttpContent into the following dto:
public class ContentDto
{
public string ContentType {get; set;}
public string Headers {get; set; }
public object Data { get; set; }
public ContentDto(HttpContent content)
{
Headers = content.Headers.Flatten();
// rest of the setup
}
}
And am running some unit tests on it:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var dto = new ContentDto(content);
var contentHeaders = content.Headers.Flatten();
Assert.Equal(contentHeaders, dto.Headers);
}
And that test fails since the Content-Length header is not being captured on my dto. However if I do:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var contentHeaders = content.Headers.Flatten();
var dto = new ContentDto(content);
Assert.Equal(contentHeaders, dto.Headers);
}
The test passes and all headers are captured. Even more I also tried this:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var dto = new ContentDto(content);
var contentHeaders = content.Headers.Flatten();
var dto1 = new ContentDto(content);
Assert.Equal(contentHeaders, dto.Headers);
Assert.Equal(contentHeaders, dto1.Headers);
}
and it fails since dto doesn't have the Content-Length header, but dto1 does. I even tried getting the headers inside a Factory-like method like this:
public static ContentDto FromContent<T>(T content) where T : HttpContent
{
// same as the constructor
}
to see if there was something special about the StringContent class regarding the Content-Length headers, but it made no difference, no matter if I used the constructor (which uses the base class HttpContent) or the generic method FromContent (using the actual StringContent in this case) the result was the same.
So my questions are:
Is that the intended behavior of HttpContent.Headers?
Are there some headers specific to the actual HttpContent type?
What am I missing here?
Note: This is the code for the Flatten extension method:
public static string Flatten(this HttpHeaders headers)
{
var data = headers.ToDictionary(h => h.Key, h => string.Join("; ", h.Value))
.Select(kvp => $"{kvp.Key}: {kvp.Value}");
return string.Join(Environment.NewLine, data)
}
Your example is incomplete. I was only able to recreate your issue when I accessed the ContentLength property before calling the extension method. Somewhere in your code (most probably //rest of setup) you are either directly or indirectly calling that property which is most probably following a lazy loading pattern and it is then included in the header when next you call your extension method and it is included in the constructed string. They don't match because you are generating your manual string before accessing the content length property.
In the source code for HttpContentHeaders.ContentLength
public long? ContentLength
{
get
{
// 'Content-Length' can only hold one value. So either we get 'null' back or a boxed long value.
object storedValue = GetParsedValues(HttpKnownHeaderNames.ContentLength);
// Only try to calculate the length if the user didn't set the value explicitly using the setter.
if (!_contentLengthSet && (storedValue == null))
{
// If we don't have a value for Content-Length in the store, try to let the content calculate
// it's length. If the content object is able to calculate the length, we'll store it in the
// store.
long? calculatedLength = _calculateLengthFunc();
if (calculatedLength != null)
{
SetParsedValue(HttpKnownHeaderNames.ContentLength, (object)calculatedLength.Value);
}
return calculatedLength;
}
if (storedValue == null)
{
return null;
}
else
{
return (long)storedValue;
}
}
set
{
SetOrRemoveParsedValue(HttpKnownHeaderNames.ContentLength, value); // box long value
_contentLengthSet = true;
}
}
you can see that if you did not explicitly set a content length then it will add it (lazy load) to the headers when you first try to access it.
This proves my original theory about it being added after you generated/flatten your string and then accessed the ContentLength property and explains the inconsistent enumeration.
It seems that the HttpContent class has a pretty strange behavior with the headers properties. Somehow the content length seems to be computed as it is stated here. It does not address your issue specifically, but you can make a test with a new httpContent object similar to the initial one. I am pretty sure that you`ll be able to get the content length without a problem.
I have an Entity Framework object returned by OData V4 controller.
I return an IQueryable and if I call the OData endpoint without any OData clause I can succesfully do this:
var content = response.Content.ReadAsAsync<IQueryable<Person>>();
And the response in JSON is the following:
{
"#odata.context":"http://xxx:8082/odata/$metadata#Persons","value":[
{
"Id":"291b9f1c-2587-4a35-993e-00033a81f6d5",
"Active":true,
"Alert":"Some alerts for the Person",
"Comments":"Some comments for the Person"
}
]
}
But as soon as I start to play with OData, for example by using $expand on a Complex property I get the following exception:
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Linq.IQueryable`1[xxx.Person]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
And the response is the following:
{
"#odata.context":"http://aqavnext01:8082/odata/$metadata#Persons","value":[
{
"Id":"291b9f1c-2587-4a35-993e-00033a81f6d5",
"Active":true,
"Alert":"Some alerts for the Person",
"Comments":"Some comments for the Person",
"Party":{
"Id":"291b9f1c-2587-4a35-993e-00033a81f6d5"
}
}
]
}
And I am deserializing using the same object returned by my Web Api so I don't understand why it fails.
Same issue when I apply $select.
Try deserialize the content like this:
var content = response.Content.ReadAsAsync<ODataResponse<Person>>();
Where ODataResponse is:
internal class ODataResponse<T>
{
public T[] Value { get; set; }
}
If you need access to the #odata.xxx fields in the response JSON (such as implementing a loop for paged results), the following is my implementation which expands on the solution from andygjp.
I'm using RestSharp as my HTTP client and Json.NET for (de)serialization. Steps as follows.
Implement a custom IRestSerializer that uses Json.NET to replace the default RestSharp serializer. Below example implementation:
public class JsonNetSerializer : IRestSerializer
{
private readonly JsonSerializerSettings _settings;
public JsonNetSerializer()
{
_settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
_settings.Converters.Add(new StringEnumConverter());
}
public string Serialize(Parameter parameter) => JsonConvert.SerializeObject(parameter.Value, Formatting.None, _settings);
public string Serialize(object obj) => JsonConvert.SerializeObject(obj, Formatting.None, _settings);
public T Deserialize<T>(IRestResponse response) => JsonConvert.DeserializeObject<T>(response.Content);
public string[] SupportedContentTypes => new string[] { "application/json", "text/json", "text/x-json", "text/javascript", "*+json" };
public DataFormat DataFormat => DataFormat.Json;
public string ContentType { get; set; } = "application/json";
}
Next, define a class that will represent your OData responses. My expanded response class - which includes the #odata.nextLink field - looks as follows.
private class ODataResponse<T>
{
public T[] Value { get; set; }
[JsonProperty("#odata.nextLink")]
public string NextLink { get; set; }
}
Finally, I create an instance of RestClient, setting up the custom serializer previously created:
var client = new RestClient("https://base.url.here/")
.UseSerializer(() => new JsonNetSerializer());
Now when I execute my request, the data in the response object object also contains my OData values.
var response = await client.ExecuteAsync<T>(request);
var nextLink = response.Data.NextLink;
I'm sure this can be done using the standard HttpClient instead of RestSharp, as the real work is done by the serializer. This was just the example implementation I had on hand.
I am using Newtonsoft JSON in my MVC based off the example cited here: Setting the Default JSON Serializer in ASP.NET MVC.
My data contract class looks something like:
[DataContract]
public MyContractClass
{
public MyContractClass()
{
this.ThisPropertyFails = new List<ClassDefinedInAnotherAssembly>();
}
[DataMember(EmitDefaultValue = false, Name = "thisPropertyIsFine")]
public string ThisPropertyIsFine { get; set; }
[DataMember(EmitDefaultValue = false, Name = "thisPropertyFails")]
public IList<ClassDefinedInAnotherAssembly> ThisPropertyFails { get; internal set; }
}
The code I'm specifically using to de-serialize looks like this:
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
{
return null;
}
var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
var bodyText = reader.ReadToEnd();
if (String.IsNullOrEmpty(bodyText))
{
return null;
}
else
{
JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new StringEnumConverter());
serializerSettings.Converters.Add(new ExpandoObjectConverter());
DictionaryValueProvider<object> result = new DictionaryValueProvider<object>(JsonConvert.DeserializeObject<ExpandoObject>(bodyText, serializerSettings), CultureInfo.CurrentCulture);
return result;
}
//return String.IsNullOrEmpty(bodyText) ? null : new DictionaryValueProvider<object>(JsonConvert.DeserializeObject<ExpandoObject>(bodyText, new ExpandoObjectConverter(), new StringEnumConverter()), CultureInfo.InvariantCulture);
}
However, in the MVC action, ModelState.IsValid is false, and looking at the errors, I see this:
{"The parameter conversion from type
'System.Collections.Generic.List`1[[System.Object, mscorlib,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'
to type 'OtherAssembly.ClassDefinedInAnotherAssembly' failed because
no type converter can convert between these types."}
Does anyone have any idea what is going on here? This same class works fine with my WebApi project (which is 'OtherAssembly' in this example).
Edit #1
Using the code directly, with the known type, does indeed work. So it's something to do with properties under ExpandoObject. For example, this code:
JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new StringEnumConverter());
serializerSettings.Converters.Add(new ExpandoObjectConverter());
JsonSerializer serializer = JsonSerializer.Create(serializerSettings);
using (StreamReader streamReader = new StreamReader(controllerContext.HttpContext.Request.InputStream))
{
using (JsonReader jsonReader = new JsonTextReader(streamReader))
{
var resultAbc = serializer.Deserialize(jsonReader, typeof(MyContractClass));
}
}
Works just fine.
Edit #2
It appears I'm not the only person to have this issue. Anyone using MVC and using the oft-cited source code to use Newtonsoft makes it impossible to de-serialize complex sub-properties:
http://tech.pro/q/34/using-jsonnet-to-deserialize-incoming-json-in-aspnet-mvc
No idea why that code is so popular if it doesn't even work after 1 level in the contract?
Too much overhead. Try :
public MyContractClass
{
[JsonProperty("thisPropertyIsFine")]
public string ThisPropertyIsFine { get; set; }
[JsonProperty("thisPropertyFails")]
public ClassDefinedInAnotherAssembly[] ThisPropertyFails { get; set; }
}
Serialize and deserialize this way
/* as Bob mentioned */
var deserialized = JsonCovert.DeserializeObject<MyContractClass>(jsonString);
var serialized = JsonCovert.SerializeObject(myContractClassInstance);
Json.NET does not serialize non-public members. See this Answer to change this behavior.
The error message "The parameter conversion from type 'X' to type 'Y' failed because no type converter can convert between these types." is coming from the ASP.Net Model Binding process, not from Json.Net's deserialization process.
To fix this error you just need to create a custom model binder for whatever type is giving you the problem (ClassDefinedInAnotherAssembly). Here is an example I'm currently using for one of my custom types called Money:
public class MoneyModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// get the result
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
Money value;
// if the result is null OR we cannot successfully parse the value as our custom type then let the default model binding process attempt it using whatever the default for Money is
return valueProviderResult == null || !Money.TryParse(valueProviderResult.AttemptedValue, out value) ? base.BindModel(controllerContext, bindingContext) : value;
}
}
TryParse from Money type:
public static bool TryParse(string value, out Money money)
{
money = null;
if (string.IsNullOrWhiteSpace(value))
return false;
decimal parsedValue;
if (decimal.TryParse(value, out parsedValue) == false)
return false;
money = parsedValue;
return true;
}
You wire up model binders in Global.asax.cs under Application_Start():
protected void Application_Start()
{
// custom model binders
ModelBinders.Binders.Add(typeof(Money), new MoneyModelBinder());
}
Any time the Model Binding process runs across a type of Money it will call this class and attempt to do the conversion.
You may want to remove your Json.Net ValueProviderFactory temporarily so its a non-factor while you resolve the model binding issue. There are lots of Json.Net ValueProviderFactorys out there. Not all are equal and may be causing an issue like not serializing dictionaries/arrays or not fully iterating an objects entire hierarchy before giving up.
How does one return unescaped Json, using Json.Net in an MVC project?
So far, I serialize a basic object, and get Json.Net to serialize it:
public JsonResult GetTimelineJson()
{
var result = new MyGraph([some data...]);
return Json(JsonConvert.SerializeObject(result), JsonRequestBehavior.AllowGet);
}
Result:
"{\r\n \"id\": \"myGraph\",\r\n \"title\": \"Graph title\",\r\n [...]
Any attempts to wrap it an an HtmlString, etc, result in an empty set being passed across the wire (though the debug point shows it correctly un-escaped). I've checked that the content-type is set correctly in the HTTP headers.
You can also do this
public ActionResult GetTimelineJson()
{
var result = new MyGraph([some data...]);
return Content(JsonConvert.SerializeObject(result), "application/json");
}
Note that you should change return type from JsonResult to ActionResult
The object is already serialized by Json.NET, and when you pass it to Json() it gets encoded twice. If you must use Json.NET instead of the built in encoder, then the ideal way to handle this would be to create a custom ActionResult accepts the object and calls Json.net internally to serialize the object and return it as an application/json result.
EDIT
This code is for the solution mentioned above. It's untested, but should work.
public class JsonDotNetResult : ActionResult
{
private object _obj { get; set; }
public JsonDotNetResult(object obj)
{
_obj = obj;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.AddHeader("content-type", "application/json");
context.HttpContext.Response.Write(JsonConvert.SerializeObject(_obj));
}
}
and in your controller just do:
return new JsonDotNetResult(result);
You are Jsoning it twice, the Json method is json serializing your already converted string. If you want to use JsonConvert then write that directly to the response stream.
I made a slight change to my new class to make unit testing easier:
public class JsonDotNetResult : ActionResult
{
public JsonDotNetResult(object data)
{
Data = data;
}
//Name the property Data and make the getter public
public object Data { get; private set; }
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.AddHeader("content-type", "application/json");
context.HttpContext.Response.Write(JsonConvert.SerializeObject(Data));
}
}
}
This more closely resembles JsonResult in System.Web.Mvc and allows me to unit test either with a generic method...
Unit test helper:
public static TReturn GetDataFromJsonResult<TJsonType, TReturn>(this ActionResult result) where TJsonType : ActionResult
{
var jsonResult = (TJsonType)result;
var data = jsonResult.GetType().GetProperty("Data").GetValue(jsonResult);
return (TReturn)data;
}
Unit Test Example:
[TestMethod]
public void ControllerMethod_WhenMethodCalled_ThenSomeRecordsAreReturned()
{
// arrange
var records = new List<string> { "Record1", "Record2" };
var expectedRecordCount = records.Count();
myService.Setup(x => x.GetRecordsFromDatabase()).Returns(records);
// act
var result = myController.GetRecords(); //Assuming this controller method returns JsonDotNetResult
// assert
var jsonResult = result.GetDataFromJsonResult<JsonDotNetResult, IEnumerable<string>>();
Assert.AreEqual(expectedRecordCount, jsonResult.Count());
}
This line can be changed if the controller return the normal JsonResult:
var jsonResult = result.GetDataFromJsonResult<JsonResult, IEnumerable<string>>();