views:

230

answers:

2

Hi, I'm having some issues trying to make a HTTP PUT (or POST) using WebClient against a MVC 2 controller. The exception is:

The parameters dictionary contains a null entry for parameter 'total'
of non-nullable type 'System.Int32' for method
'System.Web.Mvc.ActionResult Company(System.Guid, Int32, Int32, System.String)'

The controller action is:

[HttpPut]
public ActionResult Company(Guid uid, int id, int total, string url)

The route is:

routes.MapRoute(
    "CompanySet",
    "job/active/{uid}/company/{id}",
    new { controller = "Job", action = "Company" }
);

The client code is:

var putData = Encoding.ASCII.GetBytes("total=919&url=test");
var client = new WebClient();
client.UploadData("http://localhost/job/active/1/company/2", "PUT", putData);

As you may see, what I want is to send the 'uid' and 'id' parameters via url, and the 'total' and 'url' parameters as part of the PUT or POST *body*.

I've also tried to merge the latter parameters into a class (i.e., CompanySetMessage), doing it no longer raises an exception but I dont receive the values on the server side.

Any ideas? Thank you!

A: 

First, you need to set either a PUT or POST ActionMethodSelectorAttribute:

[HttpPut] // <- ActionMethodSelectorAttribute
public ActionResult Company(Guid uid, int id, int total, string url) 
{ // TODO: Bananas }

Example:

Solving the issue using forms in the view and separate methods for each request (preferred in ASP.NET MVC architecture):

Edit Company Method

    public ActionResult Company(int id)
    {
        return View(companyData.Single(x => x.Id == id)); // The filter can be anything
    }

POST Action Method

    [HttpPost]
    [ActionName("Company")] // <- Allows to create multiple Actions with the same parameters which also refer to the same ActionName
    public ActionResult Company_Post(Guid uid, int id, int total, string url)
    {
        return Content(
            String.Format("POST Values:<br />Guid: {0}<br /> Id: {1}<br /> Total: {2}<br /> Url: {3}", uid, id, total, url)
        );
    }

POST View Code (It's a strongly typed View for the type 'Company')

    <% using (Html.BeginForm()) {%>

        <%: Html.ValidationSummary(true) %>

        <fieldset>
            <legend>Fields</legend>

            <div class="editor-label">
                <%: Html.LabelFor(model => model.Uid) %>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(model => model.Uid)%>
                <%: Html.ValidationMessageFor(model => model.Uid)%>
            </div>

            <div class="editor-label">
                <%: Html.LabelFor(model => model.Id) %>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(model => model.Id) %>
                <%: Html.ValidationMessageFor(model => model.Id) %>
            </div>

            <div class="editor-label">
                <%: Html.LabelFor(model => model.Total) %>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(model => model.Total) %>
                <%: Html.ValidationMessageFor(model => model.Total) %>
            </div>

            <div class="editor-label">
                <%: Html.LabelFor(model => model.Url) %>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(model => model.Url) %>
                <%: Html.ValidationMessageFor(model => model.Url) %>
            </div>

            <p>
                <input type="submit" value="Save" />
            </p>
        </fieldset>

    <% } %>

POST Output

POST Values:
Guid: 12345678-1234-1234-1234-123456789012
Id: 1
Total: 10
Url: #1

As you can see, all values are sent back to the controller. (Without the need of collecting /initializing them first)


PUT Action Method

    [HttpPut]
    [ActionName("Company")] // <- Allows to create multiple Actions with the same parameters which also refer to the same ActionName
    public ActionResult Company_Put(Guid uid, int id, int total, string url)
    {
        return Content(
            String.Format("PUT: Values:<br />Guid: {0}<br /> Id: {1}<br /> Total: {2}<br /> Url: {3}", uid, id, total, url)
        );
    }

PUT View Code (Same View as above, just slightly changed

    <% using (Html.BeginForm()) {%>
        <%: Html.HttpMethodOverride(HttpVerbs.Put) %> // <- Only this one is new, it renders a different form
        <%: Html.ValidationSummary(true) %>
        ...

PUT View Rendered Html Code

    <form action="/dataentry/Company/1" method="post"><input name="X-HTTP-Method-Override" type="hidden" value="PUT" /> 

PUT Output

PUT: Values:
Guid: 12345678-1234-1234-1234-123456789012
Id: 1
Total: 10
Url: #1

This would be the strongly typed and safer way of sending data back and forth from controller and views. The mapping of the posted / put values is being done by the MVC framework automatically. So if you need to change something in your Domain model, you only need to add the new fields to your View and adjust the Method parameters.

Even though it isn't what you wanted, I still think it isn't that bad to have it written down somewhere :-).

Shaharyar
Hey thanks for your response -- although this doesn't resolve my issue. I've tried it and no longer raises an exception, but the controller action doesn't receive the values sent in the PUT body, it only keeps the defaults.Besides, the four parameters are mandatory, I don't want them to be optional nor have a default value.The uid and id parameters are being received from the URL, that's ok and it's working. But the other 2 -- total and url, are not.What could be the problem?
Luiggi
Oh, great you made it work! I don't really know much about the WebClient class, my bad :-).Anyhow, I altered my answer to make it work and solving this issue using a strongly typed view.
Shaharyar
A: 

Nevermind, apparently it was a problem in the client side, not the server side.

I've just changed

client.UploadData

for

client.UploadValues

using a NameValueCollection, and now it's working fine.

Luiggi