I am using Web API 2.2 with the [EnableQuery] like this:
public class ProductsController : ApiController
{
private MyContext db = new MyContext();
[EnableQuery]
public IQueryable<Product> GetProducts()
{
return db.Products;
}
}
Once I am using CamelCasePropertyNamesContractResolver I would like to perform an OData query like this: api/products/?$expand=categories instead of api/products/?$expand=Categories.
I tested the OData v4 (which I dont want use because the DateTime properties) using ODataController and it works:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();
So, I wonder if is this possible with ApiController ?
You need to do the following steps:
Define a custom EnableQueryAttribute:
public class MyEnableQueryAttribute:EnableQueryAttribute
{
public override IEdmModel GetModel(Type elementClrType, HttpRequestMessage request,
HttpActionDescriptor actionDescriptor)
{
// Get model for the request
IEdmModel model = request.ODataProperties().Model;
if (model == null)
{
// user has not configured anything or has registered a model without the element type
// let's create one just for this type and cache it in the action descriptor
model = actionDescriptor.Properties.GetOrAdd("System.Web.OData.Model+" + elementClrType.FullName, _ =>
{
ODataConventionModelBuilder builder =
new ODataConventionModelBuilder(actionDescriptor.Configuration, isQueryCompositionMode: true);
builder.EnableLowerCamelCase();
EntityTypeConfiguration entityTypeConfiguration = builder.AddEntityType(elementClrType);
builder.AddEntitySet(elementClrType.Name, entityTypeConfiguration);
IEdmModel edmModel = builder.GetEdmModel();
Contract.Assert(edmModel != null);
return edmModel;
}) as IEdmModel;
}
Contract.Assert(model != null);
return model;
}
}
Add it to the actions in the controller:
public class ProductsController : ApiController
{
[MyEnableQuery]
public IHttpActionResult Get()
{
IList<Product> products=new List<Product>();
products.Add(new Product() { Id = 1, Name = "Name1",Category=new Category(){Id=1,Name="Category1" }});
products.Add(new Product() { Id = 2, Name = "Name2", Category = new Category() { Id = 2, Name = "Category2" } });
return Ok(products.AsQueryable<Product>());
}
}
then it is able to query with camel case:
GET http://localhost:12568/api/Products?$expand=category
I've put the whole solution here: https://github.com/tanjinfu/WebApiODataSamples/tree/master/EnableCamelCaseForApiController, FYI.
Related
I would like to use ODATA. Unfortunately I get neither count nor odata.context back. Select, filter, orderBy are working though. Does anyone have an idea where my error is?
[AllowAnonymous]
[ApiController]
[Route("api/v1/wea")]
[Produces("application/json")]
[ODataRouteComponent("api/v1/wea")]
public class WeatherForecastODataController : ODataController
{
[HttpGet]
[EnableQuery]
public IEnumerable<WeatherForecast> Get()
{
_logger.LogWarning("Get Forecast");
return _forecasts;
}
}
public static IMvcBuilder AddODataOptions(this IMvcBuilder builder)
{
builder.AddOData(options => {
var defaultBatchHandler = new DefaultODataBatchHandler();
defaultBatchHandler.MessageQuotas.MaxNestingDepth = 2;
defaultBatchHandler.MessageQuotas.MaxOperationsPerChangeset = 10;
defaultBatchHandler.MessageQuotas.MaxReceivedMessageSize = 100;
var model1 = EdmBuilder.BuildV1();
options.Select().Filter().OrderBy().Count().SetMaxTop(2).Expand();
options.AddRouteComponents("api/v1", model1, defaultBatchHandler);
});
return builder;
}
I have a project with WebAPI controllers. I'm now adding OData controllers to it. The problem is that my OData controller has the same name as an existing WebAPI controller, and that leads to an exception:
Multiple types were found that match the controller named 'Member'. This can happen if the route that services this request ('OData/{*odataPath}') found multiple controllers defined with the same name but differing namespaces, which is not supported. The request for 'Member' has found the following matching controllers: Foo.Bar.Web.Areas.API.Controllers.MemberController Foo.Bar.Web.Odata.Controllers.MemberController
And this happens even though the controllers are in different namespaces and should have distinguishable routes. Here is a summary of the config that I have. What can I do (besides renaming the controller) to prevent this exception? I'm trying expose these endpoints as:
mysite.com/OData/Members
mysite.com/API/Members/EndPoint
It seems to me that the URLs are distinct enough that there's gotta be some way to configure routing so there's no conflict.
namespace Foo.Bar.Web.Odata.Controllers {
public class MemberController : ODataController {
[EnableQuery]
public IHttpActionResult Get() {
// ... do stuff with EF ...
}
}
}
namespace Foo.Bar.Web.Areas.API.Controllers {
public class MemberController : ApiControllerBase {
[HttpPost]
public HttpResponseMessage EndPoint(SomeModel model) {
// ... do stuff to check email ...
}
}
}
public class FooBarApp : HttpApplication {
protected void Application_Start () {
// ... snip ...
GlobalConfiguration.Configure(ODataConfig.Register);
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
// ... snip ...
}
}
public static class ODataConfig {
public static void Register(HttpConfiguration config) {
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "OData",
model: GetModel());
}
public static Microsoft.OData.Edm.IEdmModel GetModel() {
// ... build edm models ...
}
}
namespace Foo.Bar.Web.Areas.API {
public class APIAreaRegistration : AreaRegistration {
public override string AreaName {
get { return "API"; }
}
public override void RegisterArea(AreaRegistrationContext context) {
var route = context.Routes.MapHttpRoute(
"API_default",
"API/{controller}/{action}/{id}",
new { action = RouteParameter.Optional, id = RouteParameter.Optional }
);
}
}
}
If you have two controllers with same names and different namespaces for api and OData you can use this code. First add this class:
public class ODataHttpControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration _configuration;
private readonly Lazy<ConcurrentDictionary<string, Type>> _apiControllerTypes;
public ODataHttpControllerSelector(HttpConfiguration configuration)
: base(configuration)
{
_configuration = configuration;
_apiControllerTypes = new Lazy<ConcurrentDictionary<string, Type>>(GetControllerTypes);
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
return this.GetApiController(request);
}
private static ConcurrentDictionary<string, Type> GetControllerTypes()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var types = assemblies
.SelectMany(a => a
.GetTypes().Where(t =>
!t.IsAbstract &&
t.Name.EndsWith(ControllerSuffix, StringComparison.OrdinalIgnoreCase) &&
typeof(IHttpController).IsAssignableFrom(t)))
.ToDictionary(t => t.FullName, t => t);
return new ConcurrentDictionary<string, Type>(types);
}
private HttpControllerDescriptor GetApiController(HttpRequestMessage request)
{
var isOData = IsOData(request);
var controllerName = GetControllerName(request);
var type = GetControllerType(isOData, controllerName);
return new HttpControllerDescriptor(_configuration, controllerName, type);
}
private static bool IsOData(HttpRequestMessage request)
{
var data = request.RequestUri.ToString();
bool match = data.IndexOf("/OData/", StringComparison.OrdinalIgnoreCase) >= 0 ||
data.EndsWith("/OData", StringComparison.OrdinalIgnoreCase);
return match;
}
private Type GetControllerType(bool isOData, string controllerName)
{
var query = _apiControllerTypes.Value.AsEnumerable();
if (isOData)
{
query = query.FromOData();
}
else
{
query = query.WithoutOData();
}
return query
.ByControllerName(controllerName)
.Select(x => x.Value)
.Single();
}
}
public static class ControllerTypeSpecifications
{
public static IEnumerable<KeyValuePair<string, Type>> FromOData(this IEnumerable<KeyValuePair<string, Type>> query)
{
return query.Where(x => x.Key.IndexOf(".OData.", StringComparison.OrdinalIgnoreCase) >= 0);
}
public static IEnumerable<KeyValuePair<string, Type>> WithoutOData(this IEnumerable<KeyValuePair<string, Type>> query)
{
return query.Where(x => x.Key.IndexOf(".OData.", StringComparison.OrdinalIgnoreCase) < 0);
}
public static IEnumerable<KeyValuePair<string, Type>> ByControllerName(this IEnumerable<KeyValuePair<string, Type>> query, string controllerName)
{
var controllerNameToFind = string.Format(CultureInfo.InvariantCulture, ".{0}{1}", controllerName, DefaultHttpControllerSelector.ControllerSuffix);
return query.Where(x => x.Key.EndsWith(controllerNameToFind, StringComparison.OrdinalIgnoreCase));
}
}
It drives DefaultHttpControllerSelector and you should add this line at the end of Register method inside WebApiConfig.cs file:
config.Services.Replace(typeof(IHttpControllerSelector), new ODataHttpControllerSelector(config));
Notes:
It uses controller's namespace to determine that controller is OData or not. So you should have namespace YourProject.Controllers.OData for your OData controllers and in contrast for API controllers, it should not contains OData word in the namespace.
Thanks to Martin Devillers for his post. I used his idea and a piece of his code!
You'll want to include namespace constraint on your WebAPI:
var route = context.Routes.MapHttpRoute(
name: "API_default",
routeTemplate: "API/{controller}/{action}/{id}",
defaults:new { action = RouteParameter.Optional, id = RouteParameter.Optional },
);
route.DataTokens["Namespaces"] = new string[] {"Foo.Bar.Web.Areas.API.Controllers"];
If you are getting conflicts for view controllers, you should be able to include a similar namespace constraint as:
routes.MapRoute(
name: "ViewControllers_Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, area = "" },
namespaces: new[]{"Foo.Bar.Web.Controllers"}
);
My problem is that I wish to return camelCased (as opposed to the standard PascalCase) JSON data via ActionResults from ASP.NET MVC controller methods, serialized by JSON.NET.
As an example consider the following C# class:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
By default, when returning an instance of this class from an MVC controller as JSON, it'll be serialized in the following fashion:
{
"FirstName": "Joe",
"LastName": "Public"
}
I would like it to be serialized (by JSON.NET) as:
{
"firstName": "Joe",
"lastName": "Public"
}
How do I do this?
or, simply put:
JsonConvert.SerializeObject(
<YOUR OBJECT>,
new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
For instance:
return new ContentResult
{
ContentType = "application/json",
Content = JsonConvert.SerializeObject(new { content = result, rows = dto }, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }),
ContentEncoding = Encoding.UTF8
};
I found an excellent solution to this problem on Mats Karlsson's blog. The solution is to write a subclass of ActionResult that serializes data via JSON.NET, configuring the latter to follow the camelCase convention:
public class JsonCamelCaseResult : ActionResult
{
public JsonCamelCaseResult(object data, JsonRequestBehavior jsonRequestBehavior)
{
Data = data;
JsonRequestBehavior = jsonRequestBehavior;
}
public Encoding ContentEncoding { get; set; }
public string ContentType { get; set; }
public object Data { get; set; }
public JsonRequestBehavior JsonRequestBehavior { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (JsonRequestBehavior == JsonRequestBehavior.DenyGet && String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.");
}
var response = context.HttpContext.Response;
response.ContentType = !String.IsNullOrEmpty(ContentType) ? ContentType : "application/json";
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data == null)
return;
var jsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
response.Write(JsonConvert.SerializeObject(Data, jsonSerializerSettings));
}
}
Then use this class as follows in your MVC controller method:
public ActionResult GetPerson()
{
return new JsonCamelCaseResult(new Person { FirstName = "Joe", LastName = "Public" }, JsonRequestBehavior.AllowGet)};
}
For WebAPI, check out this link:
http://odetocode.com/blogs/scott/archive/2013/03/25/asp-net-webapi-tip-3-camelcasing-json.aspx
Basically, add this code to your Application_Start:
var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var settings = jsonFormatter.SerializerSettings;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
I think this is the simple answer you are looking for. It's from Shawn Wildermuth's blog:
// Add MVC services to the services container.
services.AddMvc()
.AddJsonOptions(opts =>
{
opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
Add Json NamingStrategy property to your class definition.
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
An alternative to the custom filter is to create an extension method to serialize any object to JSON.
public static class ObjectExtensions
{
/// <summary>Serializes the object to a JSON string.</summary>
/// <returns>A JSON string representation of the object.</returns>
public static string ToJson(this object value)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter> { new StringEnumConverter() }
};
return JsonConvert.SerializeObject(value, settings);
}
}
Then call it when returning from the controller action.
return Content(person.ToJson(), "application/json");
Simpler is better IMO!
Why don't you do this?
public class CourseController : JsonController
{
public ActionResult ManageCoursesModel()
{
return JsonContent(<somedata>);
}
}
The simple base class controller
public class JsonController : BaseController
{
protected ContentResult JsonContent(Object data)
{
return new ContentResult
{
ContentType = "application/json",
Content = JsonConvert.SerializeObject(data, new JsonSerializerSettings {
ContractResolver = new CamelCasePropertyNamesContractResolver() }),
ContentEncoding = Encoding.UTF8
};
}
}
You must set the settings in the file 'Startup.cs'
You also have to define it in the default values of JsonConvert, this is if you later want to directly use the library to serialize an object.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(options => {
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
}
Below is an action method that returns a json string (cameCase) by serializing an array of objects.
public string GetSerializedCourseVms()
{
var courses = new[]
{
new CourseVm{Number = "CREA101", Name = "Care of Magical Creatures", Instructor ="Rubeus Hagrid"},
new CourseVm{Number = "DARK502", Name = "Defence against dark arts", Instructor ="Severus Snape"},
new CourseVm{Number = "TRAN201", Name = "Transfiguration", Instructor ="Minerva McGonal"}
};
var camelCaseFormatter = new JsonSerializerSettings();
camelCaseFormatter.ContractResolver = new CamelCasePropertyNamesContractResolver();
return JsonConvert.SerializeObject(courses, camelCaseFormatter);
}
Note the JsonSerializerSettings instance passed as the second parameter. That's what makes the camelCase happen.
In ASP.NET Core MVC.
public IActionResult Foo()
{
var data = GetData();
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
return Json(data, settings);
}
I did like this :
public static class JsonExtension
{
public static string ToJson(this object value)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize
};
return JsonConvert.SerializeObject(value, settings);
}
}
this a simple extension method in MVC core , it's going to give the ToJson() ability to every object in your project , In my opinion in a MVC project most of object should have the ability to become json ,off course it depends :)
If you are returning ActionResult in .net core web api, or IHttpAction result then you can just wrap up your model in an Ok() method which will match the case on your front end and serialise it for you. No need to use JsonConvert. :)
Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson
this solve my problem
I've been trying to implement David Hayden's Orchard CMS and ASP .NET Web API http://www.davidhayden.me/blog/orchard-cms-and-asp.net-web-api, but I'm running into problems, basically getting a page not found.
This is what I have:
Under my controllers
ContactViewModel.cs
namespace Sunkist.ContactManager.Controllers
{
public class ContactsController : ApiController
{
private readonly IContentManager _contentManager;
public ContactsController(IContentManager contentManager)
{
_contentManager = contentManager;
}
public IEnumerable<ContactViewModel> Get()
{
return _contentManager
.Query(VersionOptions.Published, "Contact")
.List()
.Select(c => new ContactViewModel(c));
}
public ContactViewModel Get(int id)
{
var contact = _contentManager.Get(id);
if (contact == null)
throw new HttpResponseException
(new HttpResponseMessage(HttpStatusCode.NotFound));
return new ContactViewModel(contact);
}
}
}
ViewModel folder
ViewModel.cs
namespace Sunkist.ContactManager.ViewModel
{
public class ContactViewModel
{
private Orchard.ContentManagement.ContentItem c;
public ContactViewModel(Orchard.ContentManagement.ContentItem c)
{
// TODO: Complete member initialization
this.c = c;
}
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
}
}
and migration
namespace Sunkist.ContactManager {
public class Migrations : DataMigrationImpl {
public int Create() {
// Creating table ContactRecord
SchemaBuilder.CreateTable("ContactRecord", table => table
.ContentPartRecord()
.Column("Name", DbType.String)
.Column("Address", DbType.String)
.Column("City", DbType.String)
);
return 1;
}
}
}
I'm new to both Orchard and .Net MVC, So I'm not sure what I'm doing wrong?
Double check the name of the module that contains the Web API controller.
Via VS GUI, I created a new web api controller class "TestController.cs" and could not find the correct endpoint url.
That is, until I looked at the Module.txt in the project I added the controller to. The project is "Orchard.Users", but the name in the Module.txt file is just "Users".
I was able to hit the endpoint at "http://example.com/api/users/test".
Noteworthy: I setup a route for this at ".../UsersApi", but the 'auto-magic' URL pattern still works. Code below. Also, I later added a new module to contain my api controller and it would not work until I enabled the module in the dashboard.
TestController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace Orchard.Users.Controllers
{
public class TestController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST api/<controller>
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
}
HttpRoutes.cs
using System.Collections.Generic;
using Orchard.Mvc.Routes;
using Orchard.WebApi.Routes;
public class HttpRoutes : IHttpRouteProvider
{
public void GetRoutes(ICollection<RouteDescriptor> routes)
{
foreach (RouteDescriptor routeDescriptor in GetRoutes())
{
routes.Add(routeDescriptor);
}
}
public IEnumerable<RouteDescriptor> GetRoutes()
{
return new[] {
new HttpRouteDescriptor {
Name = "UsersApi",
Priority = -10,
RouteTemplate = "usersapi/{id}",
Defaults = new {
area = "Orchard.Users",
controller = "Test",
id = RouteParameter.Optional
},
}
};
}
}
Example of URL
http_//host/url/unlimited/index?first=value1&second=value2...&anyvalidname=somevalue
I want to have one action accepting unknown in advance amount of params with unknown names. Something like this:
public class UnlimitedController : Controller
{
public ActionResult Index(object queryParams)
{
}
//or even better
public ActionResult Index(Dictionary<string, object> queryParams)
{
}
}
You could create a custom model binder that will convert the querystrings into dictionary.
Custom Model Binder
public class CustomModelBinder: IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var querystrings = controllerContext.HttpContext.Request.QueryString;
return querystrings.Cast<string>()
.Select(s => new { Key = s, Value = querystrings[s] })
.ToDictionary(p => p.Key, p => p.Value);
}
}
Action
public ActionResult Index([ModelBinder(typeof(CustomModelBinder))]
Dictionary<string, string> queryParams)
{
}
In HomeController.cs
public ActionResult Test()
{
Dictionary<string, string> data = new Dictionary<string, string>();
foreach (string index in Request.QueryString.AllKeys)
{
data.Add(index, Request.QueryString[index]);
}
StringBuilder sb = new StringBuilder();
foreach (var element in data)
{
sb.Append(element.Key + ": " + element.Value + "<br />");
}
ViewBag.Data = sb.ToString();
return View();
}
In Test.cshtml
<h2>Test</h2>
#Html.Raw(ViewBag.Data)
Webpage, http://localhost:35268/Home/Test?var1=1&var2=2, shows:
var1: 1
var2: 2
why dont you keep everything you want inside a single query string parameter and get it on server side as string
then parse the string urself and get what ever you want
something like this
http://example.com?a=someVar&b=var1_value1__var2_value2__var3_value3
then at server side just split the string and get the variables and all the values
if you dont want this then what you can do is that
just call the controller through the url and manually get into the Request.QueryString[] collection and you will get all the variables and there values there
Your controller code could be like
public ActionResult MultipleParam(int a, int b, int c)
{
ViewData["Output"] = a + b + c;
return View();
}
Global.asax.cs
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Parameter",
"{controller}/{action}/{a}/{b}/{c}",
new { controller = "Home", action = "MultipleParam", a = 0, b = 0, c = 0 }
);
}
If the route is {controller}/{action}/{id}/{page}, then /Home/MultipleParam/101/1?showComments=true, then the retrieval mechanism would be:
public ActionResult MultipleParam(string id /* = "101" */, int page /* = 1 */, bool showComments /* = true */) { }
Another possible solution is to create custom Route
public class ParamsEnabledRoute : RouteBase
{
private Route route;
public ParamsEnabledRoute(string url)
{
route = new Route(url, new MvcRouteHandler());
}
public override RouteData GetRouteData(HttpContextBase context)
{
var data = route.GetRouteData(context);
if (data != null)
{
var paramName = (string)data.Values["paramname"] ?? "parameters";
var parameters = context.Request.QueryString.AllKeys.ToDictionary(key => key, key => context.Request.QueryString[key]);
data.Values.Add(paramName, parameters);
return data;
}
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext context, RouteValueDictionary rvd)
{
return route.GetVirtualPath(context, rvd);
}
}
Usage:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new ParamsEnabledRoute("ParamsEnabled/{controller}/{action}/{paramname}"));
}
Controller:
public class HomeController : Controller
{
public ActionResult Test(Dictionary<string, string> parameters)
{
}
}
URL:
http://localhost/ParamsEnabled/Home/Test/parameteres?param1=value1¶m2=value2
Route attribute:
public class RouteDataValueAttribute : ActionMethodSelectorAttribute
{
private readonly RouteDataValueAttributeEnum type;
public RouteDataValueAttribute(string valueName)
: this(valueName, RouteDataValueAttributeEnum.Required)
{
}
public RouteDataValueAttribute(string valueName, RouteDataValueAttributeEnum type)
{
this.type = type;
ValueName = valueName;
}
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
if (type == RouteDataValueAttributeEnum.Forbidden)
{
return controllerContext.RouteData.Values[ValueName] == null;
}
if (type == RouteDataValueAttributeEnum.Required)
{
return controllerContext.RouteData.Values[ValueName] != null;
}
return false;
}
public string ValueName { get; private set; }
}
public enum RouteDataValueAttributeEnum
{
Required,
Forbidden
}
Just use HttpContext to gather your query string.
using System.Web;
public class UnlimitedController : Controller
{
public ActionResult Index(object queryParams)
{
}
//or even better
public ActionResult Index()
{
NameValueCollection queryString = HttpContext.Request.QueryString;
// Access queryString in the same manner you would any Collection, including a Dictionary.
}
}
The question asked "How to create ASP.NET MVC controller accepting unlimited amount of parameters from query string"? Any controller will accept unlimited amount of parameters as a NamedValueCollection.