What is the default parameter type in .net core web api? - .net-core

For example, I have an API like this:
[HttpPost("device")]
public string Post(string uid, string name)
{
return "value";
}
Will this code I posted work? By default?
form-data or x-www-form-urlencoded or raw?
I know If I want to post json data I have to add the [FromBody] and the parameter into a class right?
Question 2: how to post json data to the API, but do not have to use a instance of the parameter but can use the parameter?
string uid, string deviceId

Q1: I newly created an asp.net core 3.1 api project and create a new HomeController, and it worked in my side
using Microsoft.AspNetCore.Mvc;
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
[HttpPost("device")]
public string Post(string uid, string name)
{
return "value";
}
}
}
Q2: I think there might be some way to realize your goal but I'm afraid that needs more configuration such as adding a middleware. So I'm afraid the easiest way to receive json data is using an entity which contains the parameters. And if you consists on not creating entity for that data format, maybe you can use Dictionary like code below:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
[HttpPost("device")]
public string Post([FromBody] Dictionary<string, string> test)
{
string uid = test.GetValueOrDefault("uid");
string name = test.GetValueOrDefault("name");
return "value";
}
}
}

Related

How to create a basic web api to get data

Trying to create a basic web api for my database table where anyone can view my table data using a URL (JSON format?). My understanding is that I should be able to get my data by typing in table name in URL.
my database table name is myTable which is mapped to model class My_Model
Issue: There are no errors but when i try to type in url https://localhost:7048/myTable/ it return page not found
[Route("[controller]")]
[ApiController]
public class My_Controller : Controller
{
public My_Services _services { get; }
public My_Controller(My_Services services)
{
this._services = services;
}
// Database Table Name = "myTable"
[HttpGet]
public IQueryable<My_Model> Get()
{
return (IQueryable<My_Model>)_services.Get_All_Data();
}
public IActionResult Index()
{
return View();
}
}
My_Services class - where get all data from table
public async Task<IQueryable<My_Model>> Get_All_Data()
{
IQueryable<My_Model> Query = from x in _context.My_DbSet
select x;
return Query;
}
My understanding is that I should be able to get my data by typing in
table name in URL.
No, this is not how it works. You should check Routing to controller actions in ASP.NET Core. In your example you should be able to access your data using this url: https://localhost:7048/My_. The reason is that your controller has the attribute [Route("[controller]")]. [controller] is a special value which means that the route should be the controller class name without the Controller suffix so My_ in this case.
If you want to have access using this url: https://localhost:7048/myTable then you need to either change the attribute to this: [Route("myTable")] or to change the controller class name to MyTableController.
Also your Get method looks wrong. You should await the _services.Get_All_Data method instead of casting to IQueryable<My_Model>:
[HttpGet]
public async Task<IQueryable<My_Model>> Get()
{
return await _services.Get_All_Data();
}

System.NotSupportedException when calling OData service from NetCoreApp2.1

I have set up a multi targetting (net4.5.2/netstandard2) class library allowing to consume one of our enterprise OData services.
To access this OData service we use a proxy class generated with the OData v4 Client Code Generator (v7.5.0)
Unfortunately, when trying to use my library in a Netcoreapp2.1 application I encounter an issue as soon as I try to enumerate a collection.
Container.MyDataSet.ToList(); produces the following exception :
"System.NotSupportedException : This target framework does not enable
you to directly enumerate over a data service query. This is because
enumeration automatically sends a synchronous request to the data
service. Because this framework only supports asynchronous operations,
you must instead call the BeginExecute and EndExecute methods to
obtain a query result that supports enumeration."
I do not encounter this issue when using this same multitarget library in a .Net 4.5.2 application.
Having a look at the Microsoft.OData.Client v7.5.0 source code, this behaviour seems to be by design with specific handling of the .Net Core case.
Did I miss something ?
The following code prevents the issue, but it is barely usable :
var query = (DataServiceQuery<MyData>)Container.MyDataSet;
var taskFactory = new TaskFactory<IEnumerable<MyData>>();
var t = taskFactory.FromAsync(query.BeginExecute(null, null), data => query.EndExecute(data));
t.ConfigureAwait(false);
IEnumerable<MyData> result = t.Result;
How can I use an OData IQueryable in .Net Core application without adding specific code ?
As mentioned in the error message, the platform only supports asynchronous fetches. Even after you use that, you will likely need to enumerate over the results multiple times -- everytime you perform a ToList(), FirstOrDefault() or other similar System.Generics.Collections operations, you are essentially getting the Enumerator of the collection and enumerating over it.
I adopted this solution: immediately after I fetch enumerable results from the OData libraries I enumerate over them and put them in another enumerable container (Dictionary<string, MyAwesomeResult> in this case) instantiated by me.
var resultsQuery = this.oDataClient.MyAwesomeResults
.AddQueryOption("$filter", "Name eq 'MyAwesomeName'")
.AddQueryOption("$top", "5")
.AddQueryOption("$skip", "2");
IEnumerable<MyAwesomeResult> resultsRaw = await
resultsQuery.ExecuteAsync();
var results = new Dictionary<string, MyAwesomeResult>();`
foreach (var resultRaw in resultsRaw)
{
results.Add(resultRaw.Key, resultRaw);
}
Then I use the container I instantiated -- I no longer need to enumerate again over the enumerable returned by
DataServiceQuery<MyAwesomeResult>.ExecuteAsync.
As said by #PanagiotisKanavos DataServiceQuery.ToString() will return the uri of the OData query.
Based on this, I wrote my own IQueryable :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.OData.Client;
public class ODataLinqQuery<T> : IOrderedQueryable<T>
{
public IQueryProvider Provider { get; }
private DataServiceQuery<T> DataServiceQuery { get; }
public ODataLinqQuery(DataServiceQuery<T> dataServiceQuery, MyClient client, Type finalType)
{
this.DataServiceQuery = dataServiceQuery;
this.Provider = new ODataLinqQueryProvider<T>(dataServiceQuery, client, finalType);
}
public IEnumerator<T> GetEnumerator()
{
return this.Provider.Execute<IEnumerable<T>>(this.Expression).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.Provider.Execute<System.Collections.IEnumerable>(this.Expression).GetEnumerator();
}
public Expression Expression => this.DataServiceQuery.Expression;
public Type ElementType => typeof(T);
}
Where MyClient is an utility class which wraps an HttpClient, handles authentication token, and performs result deserialization.
FinalType is to keep track on the type I want to obtain and deserialize, as I am handling IQueryables over interfaces.
Then I wrote my own IQueryProvider :
using System;
using System.Collections;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using Microsoft.OData.Client;
public class ODataLinqQueryProvider<T> : IQueryProvider
{
private MyClient Client { get; set; }
private DataServiceQuery<T> DataServiceQuery { get; set; }
private Type FinalType { get; }
public ODataLinqQueryProvider(
DataServiceQuery<T> dsq,
MyClient client,
Type finalType)
{
this.DataServiceQuery = dsq;
this.Client = client;
this.FinalType = finalType;
}
public IQueryable CreateQuery(Expression expression)
{
return new ODataLinqQuery<T>(this.DataServiceQuery, this.Client, this.FinalType);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
var pro = new DataServiceQuery<TElement>(expression, this.DataServiceQuery.Provider as DataServiceQueryProvider);
return new ODataLinqQuery<TElement>(pro, this.Client, this.FinalType);
}
public object Execute(Expression expression)
{
this.DataServiceQuery = new DataServiceQuery<T>(expression, this.DataServiceQuery.Provider as DataServiceQueryProvider);
return this.Execute();
}
public TResult Execute<TResult>(Expression expression)
{
this.DataServiceQuery = new DataServiceQuery<T>(expression, this.DataServiceQuery.Provider as DataServiceQueryProvider);
var res = this.Execute();
if (typeof(IEnumerable).IsAssignableFrom(typeof(TResult)))
{
return (TResult)res;
}
else
{
return ((IEnumerable)res).Cast<TResult>().FirstOrDefault();
}
}
private object Execute()
{
var result = Client.GetResult(typeof(OData<>).MakeGenericType(this.FinalType), HttpMethod.Get, new Uri(this.DataServiceQuery.ToString())) as OData;
return result.Objects;
}
}
Where Odata<> class is just for deserialization of the OData result and GetResult "just" invokes the GetAsync method of its underlying HttpClient with the correct authentication headers, wait for and deserializes the result :
using System.Collections.Generic;
using Newtonsoft.Json;
public class OData<T> : OData where T : class
{
public override IEnumerable<object> Objects => this.Value;
public List<T> Value { get; set; }
}
public class OData
{
[JsonProperty("#odata.context")]
public string Metadata { get; set; }
public virtual IEnumerable<object> Objects { get; set; }
}
Finally I expose my IQueryable as follows :
var myQueryable = new ODataLinqQuery<MyData>(this.Container.MyDataSet, myclient, typeof(MyData));
I can then apply filters, orderby, top and skip and get the results as with a standard IQueryable. I know that this implementation is not complete, and IQueryable to OData is not as complete as most IQueryable to SQL, but it achieves the minimum I need.

How can I get user and claim information using action filters?

Right now I am doing this to get the information I need:
In my base controller:
public int roleId { get; private set; }
public int userId { get; private set; }
public void setUserAndRole()
{
ClaimsIdentity claimsIdentity;
var httpContext = HttpContext.Current;
claimsIdentity = httpContext.User.Identity as ClaimsIdentity;
roleId = Int32.Parse(claimsIdentity.FindFirst("RoleId").Value);
userId = Int32.Parse(User.Identity.GetUserId());
}
In my controller methods:
public async Task<IHttpActionResult> getTest(int examId, int userTestId, int retrieve)
{
setUserAndRole();
I wanted the roleId and userId to be available and populated in the constructor of my class but from what I understand the constructor fires before authorization information is available.
Can someone tell me how I could do this with an Action Filter? Ideally I would like the Action Filter to be at the controller level but if not then could it be done at the method level.
I am hoping for some good advice and suggestions. Thank you
Update to show System.Web.Http
#region Assembly System.Web.Http, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// C:\H\server\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll
#endregion
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
namespace System.Web.Http.Filters
{
//
// Summary:
// Represents the base class for all action-filter attributes.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IFilter
{
//
// Summary:
// Initializes a new instance of the System.Web.Http.Filters.ActionFilterAttribute
// class.
protected ActionFilterAttribute();
//
// Summary:
// Occurs after the action method is invoked.
//
// Parameters:
// actionExecutedContext:
// The action executed context.
public virtual void OnActionExecuted(HttpActionExecutedContext actionExecutedContext);
public virtual Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken);
//
// Summary:
// Occurs before the action method is invoked.
//
// Parameters:
// actionContext:
// The action context.
public virtual void OnActionExecuting(HttpActionContext actionContext);
public virtual Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken);
}
}
Based on your method signature (and later comments below) the code assumes that you are using Web API and not MVC although this could easily be changed for MVC as well.
I do want to specify that if you look purely at the requirements its how can I create a maintainable piece of code that is reused. In this case the code gets claims based information and injects it into your controllers. The fact that you are asking for a Filter is a technical requirement but I am also going to present a solution that does not use a Filter but an IoC instead which adds some flexibility (IMHO).
Some Tips
Try to always use interfaces when/where possible. It makes for easier unit testing, easier to alter the implementation, etc. I will not go into that all here but here is a link.
In WebAPI and also MVC do not use the System.Web.HttpContext.Current. It is very hard to unit test code that makes use of this. Mvc and Web API have a common abstraction called HttpContextBase, use this when possible. If there is no other way (I have not seen this yet) then use new HttpContextWrapper(System.Web.HttpContext.Current) and pass this instance in to what ever method/class you want to use (HttpContextWrapper derives from HttpContextBase).
Proposed Solutions
These are in no particular order. See end for a basic pro list of each solution.
Web API Filter - exactly what you are asking for. A Web API action filter to inject the claims based information into your Web Api methods.
IoC/DI - A very flexible approach to injecting dependencies into your Controllers and classes. I used AutoFac as the Di framework and illustrate how you can get the claims based info injected into your controller.
Authorization Filter - Essentially an extension on solution 1 but used in a manner in which you can secure access to your Web API interface. As it was not clear how you wanted to use this information I made the jump in this proposal that you wanted it to ensure the user had sufficient privileges.
Common Code
UserInfo.cs
This is common code used in both solutions that I will demo below. This is a common abstraction around the properties / claims based info you want access to. This way you do not have to extend methods if you want to add access to another property but just extend the interface / class.
using System;
using System.Security.Claims;
using System.Web;
using Microsoft.AspNet.Identity;
namespace MyNamespace
{
public interface IUserInfo
{
int RoleId { get; }
int UserId { get; }
bool IsAuthenticated { get; }
}
public class WebUserInfo : IUserInfo
{
public int RoleId { get; set; }
public int UserId { get; set; }
public bool IsAuthenticated { get; set; }
public WebUserInfo(HttpContextBase httpContext)
{
try
{
var claimsIdentity = httpContext.User.Identity as ClaimsIdentity;
IsAuthenticated = httpContext.User.Identity.IsAuthenticated;
if (claimsIdentity != null)
{
RoleId = Int32.Parse(claimsIdentity.FindFirst("RoleId").Value);
UserId = Int32.Parse(claimsIdentity.GetUserId());
}
}
catch (Exception ex)
{
IsAuthenticated = false;
UserId = -1;
RoleId = -1;
// log exception
}
}
}
}
Solution 1 - Web API Filter
This solution demos what you asked for, a reusable Web API filter that populates the claims based info.
WebApiClaimsUserFilter.cs
using System.Web;
using System.Web.Http.Controllers;
namespace MyNamespace
{
public class WebApiClaimsUserFilterAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
// access to the HttpContextBase instance can be done using the Properties collection MS_HttpContext
var context = (HttpContextBase) actionContext.Request.Properties["MS_HttpContext"];
var user = new WebUserInfo(context);
actionContext.ActionArguments["claimsUser"] = user; // key name here must match the parameter name in the methods you want to populate with this instance
base.OnActionExecuting(actionContext);
}
}
}
Now you can use this filter by applying it to your Web API methods like an attribute or at the class level. If you want access everywhere you can also add it to the WebApiConfig.cs code like so (optional).
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new WebApiClaimsUserFilterAttribute());
// rest of code here
}
}
WebApiTestController.cs
Here how to use it in a Web API method. Note that the matching is done based on the parameter name, this has to match the name assigned in the filter actionContext.ActionArguments["claimsUser"]. Your method will now be populated with the added instance from your filter.
using System.Web.Http;
using System.Threading.Tasks;
namespace MyNamespace
{
public class WebApiTestController : ApiController
{
[WebApiClaimsUserFilterAttribute] // not necessary if registered in webapiconfig.cs
public async Task<IHttpActionResult> Get(IUserInfo claimsUser)
{
var roleId = claimsUser.RoleId;
await Task.Delay(1).ConfigureAwait(true);
return Ok();
}
}
}
Solution 2 - IoC / DI
Here is a wiki on Inversion of Control and a wiki on Dependency Injection. These terms, IoC and DI, are usually used interchangeably. In a nutshell you define dependencies, register them with a DI or IoC framework, and these dependency instances are then injected in your running code for you.
There are many IoC frameworks out there, I used AutoFac but you can use whatever you want. Following this method you define your injectibles once and get access to them wherever you want. Just by referencing my new interface in the constructor it will be injected with the instance at run time.
DependencyInjectionConfig.cs
using System.Reflection;
using System.Web.Http;
using System.Web.Mvc;
using Autofac;
using Autofac.Integration.Mvc;
using Autofac.Integration.WebApi;
namespace MyNamespace
{
public static class DependencyInjectionConfig
{
/// <summary>
/// Executes all dependency injection using AutoFac
/// </summary>
/// <remarks>See AutoFac Documentation: https://github.com/autofac/Autofac/wiki
/// Compare speed of AutoFac with other IoC frameworks: http://nareblog.wordpress.com/tag/ioc-autofac-ninject-asp-asp-net-mvc-inversion-of-control
/// </remarks>
public static void RegisterDependencyInjection()
{
var builder = new ContainerBuilder();
var config = GlobalConfiguration.Configuration;
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterControllers(typeof(DependencyInjectionConfig).Assembly);
builder.RegisterModule(new AutofacWebTypesModule());
// here we specify that we want to inject a WebUserInfo wherever IUserInfo is encountered (ie. in a public constructor in the Controllers)
builder.RegisterType<WebUserInfo>()
.As<IUserInfo>()
.InstancePerRequest();
var container = builder.Build();
// For Web API
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
// 2 lines for MVC (not web api)
var resolver = new AutofacDependencyResolver(container);
DependencyResolver.SetResolver(resolver);
}
}
}
Now we just have to call this when our application starts, this can be done in the Global.asax.cs file.
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Http;
namespace MyNamespace
{
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
DependencyInjectionConfig.RegisterDependencyInjection();
// rest of code
}
}
}
Now we can use it where ever we want.
WebApiTestController.cs
using System.Web.Http;
using System.Threading.Tasks;
namespace MyNamespace
{
public class WebApiTestController : ApiController
{
private IUserInfo _userInfo;
public WebApiTestController(IUserInfo userInfo)
{
_userInfo = userInfo; // injected from AutoFac
}
public async Task<IHttpActionResult> Get()
{
var roleId = _userInfo.RoleId;
await Task.Delay(1).ConfigureAwait(true);
return Ok();
}
}
}
Here are the dependencies you can get from NuGet for this example.
Install-Package Autofac
Install-Package Autofac.Mvc5
Install-Package Autofac.WebApi2
Solution 3 - Authorization Filter
One more solution I thought of. You never specified why you needed the user and role id. Maybe you want to check access level in the method before proceeding. If this is the case the best solution is to not only implement a Filter but to create an override of System.Web.Http.Filters.AuthorizationFilterAttribute. This allows you to execute an authorization check before your code even executes which is very handy if you have varying levels of access across your web api interface. The code I put together illustrates the point but you could extend it to add actual calls to a repository for checks.
WebApiAuthorizationClaimsUserFilterAttribute.cs
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http.Controllers;
namespace MyNamespace
{
public class WebApiAuthorizationClaimsUserFilterAttribute : System.Web.Http.Filters.AuthorizationFilterAttribute
{
// the authorized role id (again, just an example to illustrate this point. I am not advocating for hard coded identifiers in the code)
public int AuthorizedRoleId { get; set; }
public override void OnAuthorization(HttpActionContext actionContext)
{
var context = (HttpContextBase) actionContext.Request.Properties["MS_HttpContext"];
var user = new WebUserInfo(context);
// check if user is authenticated, if not return Unauthorized
if (!user.IsAuthenticated || user.UserId < 1)
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "User not authenticated...");
else if(user.RoleId > 0 && user.RoleId != AuthorizedRoleId) // if user is authenticated but should not have access return Forbidden
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden, "Not allowed to access...");
}
}
}
WebApiTestController.cs
using System.Web.Http;
using System.Threading.Tasks;
namespace MyNamespace
{
public class WebApiTestController : ApiController
{
[WebApiAuthorizationClaimsUserFilterAttribute(AuthorizedRoleId = 21)] // some role id
public async Task<IHttpActionResult> Get(IUserInfo claimsUser)
{
// code will only be reached if user is authorized based on the filter
await Task.Delay(1).ConfigureAwait(true);
return Ok();
}
}
}
Quick Comparison of Solutions
If you want flexibility go with AutoFac. You can reuse this for many of the moving parts of your solution/project. It makes for very maintainable and testable code. You can extend it very easily once its setup and running.
If you want something static and simple that is guaranteed not to change and you have minimal number of moving parts where an DI framework would be overkill then go with the Filter solution.
If you want to execute authorization checks in a single location then a custom AuthorizationFilterAttribute is the best way to go. You can add the code from the filter in solution #1 to this code if authorization passes, this way you still have access to the user information for other purposes in your code.
Edits
I added a 3rd solution to the list of possibilities.
Added a solution summary at the top of the answer.
Create a custom ActionFilter class (for OnActionExecuting):
using System.Security.Claims;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
namespace YourNameSpace
{
public class CustomActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ClaimsIdentity claimsIdentity = HttpContext.Current.User.Identity as ClaimsIdentity;
filterContext.ActionParameters["roleId"] = int.Parse(claimsIdentity.FindFirst("RoleId").Value);
filterContext.ActionParameters["userId"] = int.Parse(claimsIdentity.GetUserId());
}
}
}
Then decorate a choice of Base Controller, Controller or Action(s) (depending on the level you want to apply the custom filter), and specify roleId and userId as Action parameters:
[CustomActionFilter]
public async Task<IHttpActionResult> getTest(int roleId, int userId, int examId, int userTestId, int retrieve)
{
// roleId and userId available to use here
// Your code here
}
Hopefully that should do it.

Use TagBuilder in webform project?

I have a old webform project that I have now set to .net 4.0. I have added the System.Web.MVC ref 4.0 but the TabBuilder do still not show up as a known type?
Edit : I have also tried adding System.Web.WebPages 2.0 but that do not solve the problem.
Please test this code and see this maybe you forget using
using System.Web.Mvc;
using System.Web.Routing;
namespace MvcApplication1.Helpers
{
public static class ImageHelper
{
public static string Image(this HtmlHelper helper, string id, string url, string alternateText)
{
return Image(helper, id, url, alternateText, null);
}
public static string Image(this HtmlHelper helper, string id, string url, string alternateText, object htmlAttributes)
{
// Create tag builder
var builder = new TagBuilder("img");
// Create valid id
builder.GenerateId(id);
// Add attributes
builder.MergeAttribute("src", url);
builder.MergeAttribute("alt", alternateText);
builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
// Render tag
return builder.ToString(TagRenderMode.SelfClosing);
}
}
}

Deserializing JSON objects as List<type> not working with asmx service

I am having trouble deserializing my JSON string. I have a class of type person with public properties for sequence number of type int, first name, and last name. I want to pass an array of these objects in JSON format and have them deserialized as a list so I can loop through them on the server, but ASP.NET says something about not being supported to be deserialized as an array. I have validated the JSON I am producing, and it is valid. Is there something special about the JSON that ASP.NET needs to have before it can deserialize? The funny thing is if I serialize a list<person> object to JSON it looks exactly like the JSON I am producing. I must be missing something... To clarify, I'm using the ASP.NET Ajax library to deserialize. This is what I get back from the web service:
{"Message":"Type \u0027System.Collections.Generic.IDictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]\u0027 is not supported for deserialization of an array."
Actually unfortunately this doesn't seem to have anything to do with deserializing, it appears that you can't pass an array of JSON objects to an asmx web service. Am I correct? If you can't do that, is it possible to pass a collection of JSON objects to a web service and have them processed on the server with ASP.NET and C#?
Update:
OK, here is my code. Here is the person class:
public class person
{
public person()
{
//
// TODO: Add constructor logic here
//
}
public int seq
{
get;
set;
}
public string firstName
{
get;
set;
}
public string lastName
{
get;
set;
}
}
And here is my JSON string:
[{"seq":1,"firstName":"Chris","lastName":"Westbrook"},
{"seq":2,"firstName":"sayyl","lastName":"westbrook"}]
And here is the code I'm using
[WebMethod]
public void updatePeople(string json)
{
IList<person> people =
new JavaScriptSerializer().Deserialize<IList<person>>(json);
//do stuff...
}
I figured it out. I wasn't wrapping my JSON in an object like ASP.NET Ajax requires. For future viewers of this question, all JSON objects must be wrapped with a main object before being sent to the web service. The easiest way to do this is to create the object in JavaScript and use something like json2.js to stringify it. Also, if using an asmx web service, the objects must have a __type attribute to be serialized properly. An example of this might be:
var person=new object;
person.firstName="chris";
person.lastName="Westbrook";
person.seq=-1;
var data=new object;
data.p=person;
JSON.stringify(data);
This will create an object called p that will wrap a person object. This can then be linked to a parameter p in the web service. Lists of type person are made similarly, accept using an array of persons instead of just a single object. I hope this helps someone.
Could you show the JSON string you are trying to deserialize and the way you are using the Deserialize method? The following works fine:
using System;
using System.Collections.Generic;
using System.Web.Script.Serialization;
namespace Test
{
class Program
{
class Person
{
public int SequenceNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public static void Main()
{
string json = "[{\"SequenceNumber\":1,\"FirstName\":\"FN1\",\"LastName\":\"LN1\"},{\"SequenceNumber\":2,\"FirstName\":\"FN2\",\"LastName\":\"LN2\"}]";
IList<Person> persons = new JavaScriptSerializer()
.Deserialize<IList<Person>>(json);
Console.WriteLine(persons.Count);
}
}
}
Or even simpler, when you are doing the $.ajax(...) use
data:"{\"key\":"+JSON.stringify(json_array)+"}",
and then on the other side of the code, make your function use the parameter "object key"
[WebMethod]
public static return_type myfunction(object key){...}
SERVER SIDE
[WebMethod]
public void updatePeople(object json)
CLIENT SIDE
var person = "[{"seq":1,"firstName":"Chris","lastName":"Westbrook"}
,{"seq":2,"firstName":"sayyl","lastName":"westbrook"}]";
var str = "{'json':'" + JSON.stringify(person) + "'}";
I think the problem is what type you have to deserialize. You are trying to deserialize type
IList
but you should try to deserialize just
List
Since interface can not be instantiated this might is the root problem.

Resources