Troubleshoot MVC model binding failure - argument is null in controller - asp.net-mvc

I am trying to POST an object from a WebJob to an MVC 4 controller. I am using Entity Framework. In the controller, I cannot get the object to bind properly (the argument is null). I have looked at many tutorials and it seems like my code should work.
Model (does this need to be in a specific namespace for EF to find it?):
public class CreateListingObject
{
public Listing listing;
public List<GalleryImage> images;
public CreateListingObject()
{
listing = new Listing();
images = new List<GalleryImage>();
}
}
public struct GalleryImage
{
public string picURL;
public string caption;
}
POST:
public void PostListing(CreateListingObject o)
{
Console.WriteLine("Posting listing: {0}", o.listing.Title);
HttpClient _httpClient = new HttpClient();
Uri uri = new Uri(_serviceUri, "/Automaton/CreateTestListing");
string json = BizbotHelper.SerializeJson(o);
HttpResponseMessage response = BizbotHelper.SendRequest(_httpClient, HttpMethod.Post, uri, json);
string r = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
}
SendRequest (thank you Azure search samples):
public static HttpResponseMessage SendRequest(HttpClient client, HttpMethod method, Uri uri, string json = null)
{
UriBuilder builder = new UriBuilder(uri);
//string separator = string.IsNullOrWhiteSpace(builder.Query) ? string.Empty : "&";
//builder.Query = builder.Query.TrimStart('?') + separator + ApiVersionString;
var request = new HttpRequestMessage(method, builder.Uri);
if (json != null)
{
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
}
return client.SendAsync(request).Result;
}
Controller Action fragment (o is an empty object here):
[HttpPost]
public ActionResult CreateTestListing(CreateListingObject o)
{
Listing li = o.listing;
I have confirmed that if I post a simple object using the same code, everything works as expected.
Instead of sending a CreateListingObject in PostListing, I send this instead:
var test = new
{
data = "hi mom"
};
And change my action to, then the argument gets bound and I get valid data:
[HttpPost]
public ActionResult CreateTestListing(string data)
{
I have also checked the serialization of my CreateListingObject in the WebJob, and it is fully populated as I expect. This leads me to suspect that I am falling afoul of the default ModelBinder.

Related

Web api post - passed string value is null at server side

My project is brand new Asp.net 2015 MVC6 beta 8 web application.
I get value as null when I call Web api with post type from C# code.
My server side code:
// POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
if( null != value )
do something;
}
My clientside is:
StringContent cstrJson = new StringContent("{ mesage: hello}"
, System.Text.Encoding.Unicode, "application/x-www-form-urlencoded");
var result = await client1.PostAsync("http://localhost:68888/api/myApi/", cstrJson);
I tried all different combinations of encoding and media, but no improvements.
It's null because the body couldn't be parsed as a string. The content type is application/x-www-form-urlencoded instead of text/plain.
You may want to rethink using a string anyway if your client is sending json, you should accept application/json on the server and let the framework parse it for you.
[HttpPost]
public void Post(MyObject value)
{
var msg = value.Message;
}
public class MyObject
{
public string Message { get; set; }
}
Client Side:
var cstrJson = new StringContent("{'Message' : 'hello'}", System.Text.Encoding.Unicode, "application/json");
var result = await client1.PostAsync("http://localhost:68888/api/myApi/", cstrJson);

Why does one Web API method work, whereas the other does not?

One of my Web API methods works perfectly, and the other not at all.
By works perfectly, I mean this:
The other one, though, doesn't seem to even know about itself. It answers the browser request with:
The code seems to be set up the same for both of them, so I don't know why one works like a charm and the other fails so thuddily.
The pertinent code is:
CONTROLLER
public class DepartmentsController : ApiController
{
private readonly IDepartmentRepository _deptsRepository;
public DepartmentsController(IDepartmentRepository deptsRepository)
{
if (deptsRepository == null)
{
throw new ArgumentNullException("deptsRepository is null");
}
_deptsRepository = deptsRepository;
}
[Route("api/Departments/Count")]
public int GetCountOfDepartmentRecords()
{
return _deptsRepository.Get();
}
[Route("api/Departments")]
public IEnumerable<Department> GetBatchOfDepartmentsByStartingID(int ID, int CountToFetch)
{
return _deptsRepository.Get(ID, CountToFetch);
}
REPOSITORY
public class DepartmentRepository : IDepartmentRepository
{
private readonly List<Department> departments = new List<Department>();
public DepartmentRepository()
{
using (var conn = new OleDbConnection(
#"Provider=Microsoft.ACE.OLEDB.12.0;User ID=Freebo;Password=RunningOnEmpty;Data Source=C:\CDBWin\DATA\CCRDAT42.MDB;Jet OLEDB:System database=C:\CDBWin\Data\nrbq.mdw"))
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT td_department_accounts.dept_no, IIF(ISNULL(t_accounts.name),'No Name provided',t_accounts.name) AS name FROM t_accounts INNER JOIN td_department_accounts ON t_accounts.account_no = td_department_accounts.account_no ORDER BY td_department_accounts.dept_no";
cmd.CommandType = CommandType.Text;
conn.Open();
int i = 1;
using (OleDbDataReader oleDbD8aReader = cmd.ExecuteReader())
{
while (oleDbD8aReader != null && oleDbD8aReader.Read())
{
int deptNum = oleDbD8aReader.GetInt16(0);
string deptName = oleDbD8aReader.GetString(1);
Add(new Department { Id = i, AccountId = deptNum, Name = deptName });
i++;
}
}
}
}
}
public int Get()
{
return departments.Count;
}
private Department Get(int ID) // called by Delete()
{
return departments.First(d => d.Id == ID);
}
If entering:
http://shannon2:28642/api/Departments/Count
in the browser works to execute the Controller's GetCountOfDepartmentRecords() method, why does entering:
http://localhost:28642/api/Departments/5/6
(or:
http://localhost:28642/api/Departments/1/5
etc) not work to execute the Controller's GetBatchOfDepartmentsByStartingID() method?
Your route is missing its parameters.
[Route("api/Departments/{ID:int}/{CountToFetch:int}")]
This question looks similar to your other question below:
Why is my Web API call returning "No action was found on the controller 'DPlatypus' that matches the request"?
If you are expecting the values to come from a non-query string part of a url, you need to define them in the route template. So, it should be
[Route("api/Departments/{id}/{countToFetch}")]
Following is a good article to read about routing and action selection in Web API:
http://www.asp.net/web-api/overview/web-api-routing-and-actions

Web Api Post error -> Value cannot be null. Parameter name: uriString

I am relatively new to Web Api and I am having trouble POSTing a Person object. If I run in debug, I see that my uriString never gets set and I don't understand why. Because of this, I get "400 Bad Request" errors in Fiddler for all attempted Posts.
I have tried replicating what others have done when it comes to the Post action. Every example I've found uses a repository to add the person to the database. I do not have repositories however, but instead am using the NHibernate Save method to carry out this functionality. Here are the domain class, mapping by code file, WebApiConfig, and the PersonController.
public class Person
{
public Person() { }
[Required]
public virtual string Initials { get; set; }
public virtual string FirstName { get; set; }
public virtual char MiddleInitial { get; set; }
public virtual string LastName { get; set; }
}
public class PersonMap : ClassMapping<Person>
{
public PersonMap()
{
Table("PERSON");
Lazy(false);
Id(x => x.Initials, map => map.Column("INITIALS"));
Property(x => x.FirstName, map => map.Column("FIRST_NAME"));
Property(x => x.MiddleInitial, map => map.Column("MID_INITIAL"));
Property(x => x.LastName, map => map.Column("LAST_NAME"));
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.Services.Replace(typeof(IHttpActionSelector), new HybridActionSelector());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{action}/{actionid}/{subaction}/{subactionid}",
defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional,
actionid = RouteParameter.Optional, subaction = RouteParameter.Optional, subactionid = RouteParameter.Optional }
);
config.BindParameter( typeof( IPrincipal ), new ApiPrincipalModelBinder() );
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing();
}
}
public class PersonsController : ApiController
{
private readonly ISessionFactory _sessionFactory;
public PersonsController (ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
// POST api/persons
[HttpPost]
public HttpResponseMessage Post(Person person)
{
var session = _sessionFactory.GetCurrentSession();
using (var tx = session.BeginTransaction())
{
try
{
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
var result = session.Save(person);
var response = Request.CreateResponse<Person>(HttpStatusCode.Created, person);
string uriString = Url.Route("DefaultApi", new { id = person.Initials });
response.Headers.Location = new Uri(uriString);
tx.Commit();
return response;
}
catch (Exception)
{
tx.Rollback();
}
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
}
}
Fiddler information:
POST //localhost:60826/api/employees HTTP/1.1
Request Headers:
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:xxxxx
Content-Length: 71
Request Body:
{
"Initials":"MMJ",
"LastName":"Jordan",
"FirstName":"Michael"
}
This line never sets the uriString to the correct value. string uriString = Url.Route("DefaultApi", new { id = person.Initials });
I've also tried using Url.Link instead of Url.Route. I've tried adding the controller = "Persons" inside the 'new' block, but that had no effect. Why isn't uriString being set? I'll listen to any thoughts at this point.
EDIT
I have tried
string uriString = Url.Link("DefaultApi", new { controller = "Persons", id = person.Initials, action="", actionid="", subaction="", subactionid="" });
as well as using a separate routeconfig
config.Routes.MapHttpRoute(
name: "PostApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional
} );
with
string uriString = Url.Link("PostApi", new { controller = "Persons", id = person.Initials});
and have had no luck.
SOLUTION
I was able to get this Post to work by using the line of code below. I'm not entirely sure if this is the correct way to do it, so if anybody knows differently, please share. Otherwise, I will happily use this approach.
response.Headers.Location = new Uri(this.Request.RequestUri.AbsoluteUri + "/" + person.Initials);
Problem seems to be here:
string uriString = Url.Route("DefaultApi", new { id = person.Initials });
You are only passing id while you need to be passing other parameters such as controller, etc.
You may construct URL this way:
string uriString = Url.Action("ActionName", "ControllerName", new { Id = person.Initials });

MVC3 - posting byte array to a controller - Database RowVersion

I am working on an MVC3 application. My client side ViewModel contains a SQL Server RowVersion property, which is a byte[]. It is rendered as an Object array on the client side. When I attempt to post my view model to a controller, the RowVersion property is always null.
I am assuming that the Controller serializer (JsonValueProviderFactory) is ignoring the Object array property.
I have seen this blog, however this does not apply, as I am posting JSON and not the form markup:
http://thedatafarm.com/blog/data-access/round-tripping-a-timestamp-field-with-ef4-1-code-first-and-mvc-3/
My view renders my viewmodel like so:
<script type="text/javascript">
var viewModel = #Html.Raw( Json.Encode( this.Model ) );
</script>
I then post the viewModel to the controller like so:
var data = {
'contact': viewModel
};
$.ajax({
type: 'POST',
url: '/Contact/Save',
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: 'json',
success: function (data) {
// Success
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(XMLHttpRequest.responseText);
}
});
Here is my action in the controller:
[HttpPost]
public JsonResult Save(Contact contact) {
return this.Json( this._contactService.Save( contact ) );
}
UPDATE: based on Darin's answer.
I was hoping for a cleaner solution, but since Darin provided the only answer, I will have to add a custom property that will serialize my byte[] "row_version" property to a Base64 string. And when the Base64 string is set to the new custom property, it converts the string back to a byte[]. Below is the custom "RowVersion" property that I added to my model:
public byte[] row_version {
get;
set;
}
public string RowVersion {
get {
if( this.row_version != null )
return Convert.ToBase64String( this.row_version );
return string.Empty;
}
set {
if( string.IsNullOrEmpty( value ) )
this.row_version = null;
else
this.row_version = Convert.FromBase64String( value );
}
}
My client side ViewModel contains a SQL Server RowVersion property, which is a byte[]
Make it so that instead of a byte[] your view model contains a string property which is the base64 representation of this byte[]. Then you won't have any problems roundtripping it to the client and back to the server where you will be able to get the original byte[] from the Base64 string.
Json.NET automatically encodes byte arrays as Base64.
You can use JsonNetResult instead of JsonResult:
from https://gist.github.com/DavidDeSloovere/5689824:
using System;
using System.Web;
using System.Web.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public class JsonNetResult : JsonResult
{
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
var response = context.HttpContext.Response;
response.ContentType = !string.IsNullOrEmpty(this.ContentType) ? this.ContentType : "application/json";
if (this.ContentEncoding != null)
{
response.ContentEncoding = this.ContentEncoding;
}
if (this.Data == null)
{
return;
}
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
jsonSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
var formatting = HttpContext.Current != null && HttpContext.Current.IsDebuggingEnabled ? Formatting.Indented : Formatting.None;
var serializedObject = JsonConvert.SerializeObject(Data, formatting, jsonSerializerSettings);
response.Write(serializedObject);
}
}
Usage:
[HttpPost]
public JsonResult Save(Contact contact) {
return new JsonNetResult { Data = _contactService.Save(contact) };
}

Using Moq to test methods that accept non-primative arguments

I'm trying to write a test for an ASP.Net MVC controller action.
I'd like to test that the action invokes a particular method on an injected service, so I'm mocking the service and using .Verify.
So in the simple case, I have the following action in my controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(string title)
{
_cmsService.AddPage(title);
return View("Edit");
}
using the service interface...
public interface ICmsService
{
void AddPage(string request);
}
and the following test...
//Arrange
const string pageTitle = "Test Page";
var cmsService = new Mock<ICmsService>();
cmsService.Setup(service => service.AddPage(pageTitle));
var controller = new PageController(cmsService.Object);
//Act
var result = controller.Create(pageTitle) as ViewResult;
//Assert
cmsService.Verify(service => service.AddPage(pageTitle), Times.Once());
Now I want to refactor my service operation to use request and response objects...
public interface ICmsService
{
CmsServiceAddPageResponse AddPage(CmsServiceAddPageRequest request);
}
So I change my action accordingly...
public ActionResult Create(string title)
{
var request = new CmsServiceAddPageRequest()
{
PageName = title
};
var response = _cmsService.AddPage(request);
return View("Edit");
}
and also my test...
//Arrange
const string pageTitle = "Test Page";
var cmsService = new Mock<ICmsService>();
var request = new CmsServiceAddPageRequest() {PageName = pageTitle};
cmsService.Setup(service => service.AddPage(request));
var controller = new PageController(cmsService.Object);
//Act
var result = controller.Create(pageTitle) as ViewResult;
//Assert
cmsService.Verify(service => service.AddPage(request), Times.Once());
But now when I run the test, I get the following message...
TestCase 'Web.Test.PageControllerTest.CreateNewPagePost'
failed: Moq.MockException :
Invocation was performed more than once on the mock: service => service.AddPage(value(Web.Test.PageControllerTest+<>c__DisplayClass1).request)
at Moq.Mock.ThrowVerifyException(IProxyCall expected, Expression expression, Times times)
at Moq.Mock.VerifyCalls(Interceptor targetInterceptor, MethodCall expected, Expression expression, Times times)
at Moq.Mock.Verify[T,TResult](Mock mock, Expression`1 expression, Times times, String failMessage)
at Moq.Mock`1.Verify[TResult](Expression`1 expression, Times times)
PageControllerTest.cs(67,0): at Web.Test.PageControllerTest.CreateNewPagePost()
What should I be doing to test a method that accepts a non-primitive type?
Thanks
Sandy
I think a better alternative to the first answer would be to implement a custom matcher rather than change code to match your testing framework. From:http://code.google.com/p/moq/wiki/QuickStart
// custom matchers
mock.Setup(foo => foo.Submit(IsLarge())).Throws<ArgumentException>();
...
public string IsLarge()
{
return Match<string>.Create(s => !String.IsNullOrEmpty(s) && s.Length > 100);
}
If you override Equals in the CmsServiceAddPageRequest object it should compare them correctly.

Resources