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

asp.net-web-apiasp.net-web-api-routingpost

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);

Best Answer

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.