marcus
marcus

Reputation: 10096

ASP.NET Web API controller can't handle dynamic objects?

In my code I have a base class Foo and all my objects inherits from the Foo object. So let's say I have a class like this

public class Bar : Foo {
    public string Heading { get;set; }
}

I have tried to use the ApiControllers put method with dynamic but I get this error http://paste2.org/p/1914054

This is the code I'm using in the ApiController

public void Put(string id, dynamic model) {
    //do stuff
}

If I use a normal controller I can use dynamic to post data. Is it possible to add make the api controller work with dynamic or do I need to build my own model binder?

It sees like some thinks that even in MVC 3 the input parameters can't be a dynamic but that is not true and that's why I ask this question. This controller in MVC 3 works just great with dynamic as input parameter.

Upvotes: 3

Views: 7686

Answers (3)

Himanshu Patel
Himanshu Patel

Reputation: 764

http://www.binaryintellect.net/articles/589f6915-f4c0-4bf9-9f94-4d00bd445c16.aspx

This solution works perfectly fine.

I had an HTML form where all the input controls were dynamic. Had a hard time plumbing the form data to MVC controller and then again to Web Api 2 controller.

Your server side web api method needs to be like

public string Post(FormDataCollection form){
... }

FormDataCollection resides in System.Net.Http.Formatting namespace.

If you are posing data directly from a web page (jquery), then the above link solution is works fine.

If you posting data web page to an MVC page (c# code) which is then further posted to a web api method, then the code looks like this:

[HttpPost]
    public async Task<string> MasterDraw(FormCollection body)
    {
        HttpClient client = new HttpClient();
        KeyValuePair<string, string>[] list = null;
        string url = ConfigurationManager.AppSettings["BaseServiceUrl"] + "/api/MasterOperation/addrecord";

        if (body != null && body.HasKeys())
        {
            list = new KeyValuePair<string, string>[body.AllKeys.Count()];
            for (int ctr = 0; ctr < body.Keys.Count; ctr++ )
            {
                list[ctr] = new KeyValuePair<string, string>(body.Keys[ctr], body.GetValue(body.Keys[ctr]).AttemptedValue);
            }
        }

        var content = new FormUrlEncodedContent(list);

        HttpResponseMessage response = await client.PostAsync(url, content);

        // Check that response was successful or throw exception
        response.EnsureSuccessStatusCode();

        // Read response asynchronously as JToken and write out top facts for each country
        string contentRes = response.Content.ReadAsStringAsync().Result;

        return contentRes;
    }

The browser side code:

$('#btn-save').on('click', function (event) {
            var postUrl = "@Url.Action("masterdraw","masterrender")";

            var result = $.ajax({
                type: "POST",
                data: $('#form').serialize(),
                url: postUrl                    
            });

            // insert your jquery .done and .error methods

        });

Upvotes: 0

Scott Hanselman
Scott Hanselman

Reputation: 17692

Hm, I was able to just do this with an ASP.NET Web API method:

    public string Post(dynamic value)
    {
        string s = "";
        foreach (dynamic item in value)
        {
            s = s + item.content + " ";
        }
        return s;
    }

using a JSON array:

POST http://localhost:6946/api/values HTTP/1.1
User-Agent: Fiddler
Host: localhost:6946
Content-Length: 327
Content-Type: application/json
[{"content":"Hello","editing":false},{"content":"sfsdf","editing":false},{"content":"sadsdfdsf","editing":false},{"content":"dfsdf","editing":false},{"content":"dsfsd","editing":false},{"content":"sdfsdf","editing":false},{"content":"dsf","editing":false},{"content":"dfg","editing":false},{"content":"fsdfsd","editing":false}]

And it worked....

Upvotes: 3

Darin Dimitrov
Darin Dimitrov

Reputation: 1039080

It sees like some thinks that even in MVC 3 the input parameters can't be a dynamic

I think so. Let's take a look at the provided example:

[HttpPost]
[ValidateInput(false)]
public virtual ActionResult Update(dynamic editorModel) {

    if (!TryUpdateModel(_model, "CurrentModel")) {
        var parentId = _model.Parent != null ? (string)_model.Parent.Id : null;
        var viewModel = new EditViewModel
        {
            RootModel = _session.Query<IPageModel>()
                .Where(model => model.Parent == null)
                .SingleOrDefault(),
            CurrentModel = _model,
            ParentModel = parentId != null ? _session.Load<IPageModel>(parentId) : null,
        };
        return View("edit", viewModel);
    }

    UpdateModel(_model);

    _model.Metadata.Changed = DateTime.Now;
    _model.Metadata.Published = _model.Metadata.IsPublished ? DateTime.Now : default(DateTime?);
    _model.Metadata.ChangedBy = HttpContext.User.Identity.Name;

    _repository.SaveChanges();
    _repository.Refresh(_model);

    var page = _model as IPageModel;

    if (page.Parent != null) {
        _model = _repository.SingleOrDefault<IPageModel>(m => m.Id == page.Parent.Id);
    }

    return RedirectToAction("index", new { model = _model });
}

Can you point me how/where exactly is this editorModel dynamic variable used inside this controller action?

And to even further simplify this controller action, it works, because it never never uses the dynamic variable passed as argument. I have simplified it to better illustrate what this action is roughly doing concerning model binding (throwing away of course all the infrastructure noise that we are not interested in here to illustrate the problem):

[HttpPost]
public ActionResult Update(dynamic blablabla)
{
    dynamic model = new MyViewModel();
    UpdateModel(model);
    // at this stage the model will be correctly bound

    return View(model);
}

Inside this action the TryUpdateModel and UpdateModel methods are called on the _model instance variable which is passed in the constructor and is of type IPageModel. ASP.NET MVC cannot possibly know (without a custom model binder of course) the type of your dynamic action argument. Just run this code, put a breakpoint inside the Update action and observe the type of the editorModel variable. It will simply be System.Object. There are no miracles.

So it's for me it's perfectly normal that this works the same in ASP.NET Web API.

Upvotes: 1

Related Questions