ASP.NET MVC Model Binding - c#

If i have a Controller Action that may recieve both HTTP GET and HTTP POST from a number of different sources with each source sending different data e.g.
Source1 performs a form POST with two form items Item1 and Item2
Source2 performs a GET where the data is contained in the query string (?ItemX=2&ItemY=3)
Is it possible to have a controller action that will cater for all these cases and perform binding automatically e.g.
public ActionResult Test(Dictionary data)
{
// Do work ...
return View();
}
Is this possible with a custom binder or some other way?
Dont want to work directly with HttpContext.Request if possible

The usual pattern is to have two controller methods One controller method handles the GET, the other controller method handles the POST:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult MyControllerMethod(string itemX, string itemY)
{
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MyControllerMethod(MyViewDataObject data)
{
}
If you need help binding lists, collections or dictionaries you can find it here.

This solution works, not best for unit testing
public object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
TestIBE.Models.IBERequest _IBERequest;
HttpContextBase _httpContext;
Dictionary<string, string> _requestData;
_httpContext = controllerContext.HttpContext;
_requestData = this.CreateRequestData(_httpContext.Request);
_IBERequest = new TestIBE.Models.IBERequest(
_httpContext.Session.SessionID,
_httpContext.Request.UserHostAddress,
_httpContext.Request.UserAgent,
_httpContext.Request.Url,
_requestData);
return _IBERequest;
}
private Dictionary<string, string> CreateRequestData(
HttpRequestBase subject)
{
Dictionary<string, string> _result;
_result = new Dictionary<string, string>();
subject.Form.AllKeys.ForEach(key => _result.Add(key, subject.Form[key]));
subject.QueryString.AllKeys.ForEach(key => { if (!_result.ContainsKey(key)) { _result.Add(key, subject.QueryString[key]); } });
return _result;
}
public class IBEController : Controller
{
public ActionResult Landing(
[ModelBinder(typeof(TestIBE.Helpers.Binders.IBEModelBinder))] TestIBE.Models.IBERequest IBERequest)
{
// TODO
return View();
}
}

Related

Howto read URI Params from querystring ASPNET 5 [duplicate]

I'm building one RESTful API using ASP.NET Core MVC and I want to use querystring parameters to specify filtering and paging on a resource that returns a collection.
In that case, I need to read the values passed in the querystring to filter and select the results to return.
I've already found out that inside the controller Get action accessing HttpContext.Request.Query returns one IQueryCollection.
The problem is that I don't know how it is used to retrieve the values. In truth, I thought the way to do was by using, for example
string page = HttpContext.Request.Query["page"]
The problem is that HttpContext.Request.Query["page"] doesn't return a string, but a StringValues.
Anyway, how does one use the IQueryCollection to actually read the querystring values?
You can use [FromQuery] to bind a particular model to the querystring:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding
e.g.
[HttpGet()]
public IActionResult Get([FromQuery(Name = "page")] string page)
{...}
You could use the ToString method on IQueryCollection which will return the desired value if a single page parameter is specified:
string page = HttpContext.Request.Query["page"].ToString();
if there are multiple values like ?page=1&page=2 then the result of the ToString call will be 1,2
But as #mike-g suggested in his answer you would better use model binding and not directly accessing the HttpContext.Request.Query object.
ASP.NET Core will automatically bind form values, route values and query strings by name. This means you can simply do this:
[HttpGet()]
public IActionResult Get(int page)
{ ... }
MVC will try to bind request data to the action parameters by name ... below is a list of the data sources in the order that model binding looks through them
Form values: These are form values that go in the HTTP request using the POST method. (including jQuery POST requests).
Route values: The set of route values provided by Routing
Query strings: The query string part of the URI.
Source: Model Binding in ASP.NET Core
FYI, you can also combine the automatic and explicit approaches:
[HttpGet()]
public IActionResult Get(int page
, [FromQuery(Name = "page-size")] int pageSize)
{ ... }
Here is a code sample I've used (with a .NET Core view):
#{
Microsoft.Extensions.Primitives.StringValues queryVal;
if (Context.Request.Query.TryGetValue("yourKey", out queryVal) &&
queryVal.FirstOrDefault() == "yourValue")
{
}
}
You can just create an object like this:
public class SomeQuery
{
public string SomeParameter { get; set; }
public int? SomeParameter2 { get; set; }
}
And then in controller just make something like that:
[HttpGet]
public IActionResult FindSomething([FromQuery] SomeQuery query)
{
// Your implementation goes here..
}
Even better, you can create API model from:
[HttpGet]
public IActionResult GetSomething([FromRoute] int someId, [FromQuery] SomeQuery query)
to:
[HttpGet]
public IActionResult GetSomething(ApiModel model)
public class ApiModel
{
[FromRoute]
public int SomeId { get; set; }
[FromQuery]
public string SomeParameter { get; set; }
[FromQuery]
public int? SomeParameter2 { get; set; }
}
StringValues is an array of strings. You can get your string value by providing an index, e.g. HttpContext.Request.Query["page"][0].
IQueryCollection has a TryGetValue() on it that returns a value with the given key. So, if you had a query parameter called someInt, you could use it like so:
var queryString = httpContext.Request.Query;
StringValues someInt;
queryString.TryGetValue("someInt", out someInt);
var daRealInt = int.Parse(someInt);
Notice that unless you have multiple parameters of the same name, the StringValues type is not an issue.
in .net core if you want to access querystring in our view use it like
#Context.Request.Query["yourKey"]
if we are in location where #Context is not avilable we can inject it like
#inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
#if (HttpContextAccessor.HttpContext.Request.Query.Keys.Contains("yourKey"))
{
<text>do something </text>
}
also for cookies
HttpContextAccessor.HttpContext.Request.Cookies["DeniedActions"]
Maybe it helps.
For get query string parameter in view
View:
#inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
#{ Context.Request.Query["uid"]}
Startup.cs ConfigureServices :
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
I have a better solution for this problem,
request is a member of abstract class ControllerBase
GetSearchParams() is an extension method created in bellow helper
class.
var searchparams = await Request.GetSearchParams();
I have created a static class with few extension methods
public static class HttpRequestExtension
{
public static async Task<SearchParams> GetSearchParams(this HttpRequest request)
{
var parameters = await request.TupledParameters();
try
{
for (var i = 0; i < parameters.Count; i++)
{
if (parameters[i].Item1 == "_count" && parameters[i].Item2 == "0")
{
parameters[i] = new Tuple<string, string>("_summary", "count");
}
}
var searchCommand = SearchParams.FromUriParamList(parameters);
return searchCommand;
}
catch (FormatException formatException)
{
throw new FhirException(formatException.Message, OperationOutcome.IssueType.Invalid, OperationOutcome.IssueSeverity.Fatal, HttpStatusCode.BadRequest);
}
}
public static async Task<List<Tuple<string, string>>> TupledParameters(this HttpRequest request)
{
var list = new List<Tuple<string, string>>();
var query = request.Query;
foreach (var pair in query)
{
list.Add(new Tuple<string, string>(pair.Key, pair.Value));
}
if (!request.HasFormContentType)
{
return list;
}
var getContent = await request.ReadFormAsync();
if (getContent == null)
{
return list;
}
foreach (var key in getContent.Keys)
{
if (!getContent.TryGetValue(key, out StringValues values))
{
continue;
}
foreach (var value in values)
{
list.Add(new Tuple<string, string>(key, value));
}
}
return list;
}
}
in this way you can easily access all your search parameters. I hope this will help many developers :)
Some of the comments mention this as well, but asp net core does all this work for you.
If you have a query string that matches the name it will be available in the controller.
https://myapi/some-endpoint/123?someQueryString=YayThisWorks
[HttpPost]
[Route("some-endpoint/{someValue}")]
public IActionResult SomeEndpointMethod(int someValue, string someQueryString)
{
Debug.WriteLine(someValue);
Debug.WriteLine(someQueryString);
return Ok();
}
Ouputs:
123
YayThisWorks
Startup.cs add this service
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Your view add inject #inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
get your value
Code
#inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
#{
var id = HttpContextAccessor.HttpContext.Request.RouteValues["id"];
if (id != null)
{
// parameter exist in your URL
}
}
In case you want to access QueryString inside of an asp.net core view you can do it like this:
#inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
#if (Context.Request.Query.Keys.Any())
{
<button>--ClearFilters--</button>
}
we usually can fetch data from routing in 3 way:
1.query string
2.query params
3.hybrid
I describe query string:
exp:
[HttpGet("Home/routing")]
public IActionResult privacy(String name)
{
return ViewModel:name
}
to pass name as querystring:
url:port/Home/routing?name=Alex

Asp.net core Cleanest way to return View or Json/XML

In asp.net core I would like to set up my API controller to do the following:
by default return View(model);
/api/id.json to return model; as json
/api/id.xml to return model; as xml
The second two can be achieved by using the [FormatFilter] see here
[FormatFilter]
public class ProductsController
{
[Route("[controller]/[action]/{id}.{format?}")]
public Product GetById(int id)
However this requires the method to return an object and not a View(object). Is there anyway to cleanly support also returning Views?
You cannot do both in the same action. However, you can factor out the common functionality into a private method and then implement two actions with minimal code duplication:
[Route("[controller]")]
[FormatFilter]
public class ProductsController : Controller
{
private Product GetByIdCore(int id)
{
// common code here, return product
}
[HttpGet("[action]/{id}")]
[ActionName("GetById")]
public IActionResult GetByIdView(int id) => View(GetByIdCore(id));
[HttpGet("[action]/{id}.{format}")]
public Product GetById(int id) => GetByIdCore(id);
}
It's necessary to use different action names here, because the method signatures cannot differ merely on return type. However, the [ActionName] attribute can be used as above to make them appear to have the same name for the purposes of URL generation and such.
You can actually achieve this just using the one action. Here's an example of how I got it to work:
[FormatFilter]
public class ProductsController : Controller
{
[Route("[controller]/[action]/{id}.{format?}")]
public IActionResult GetById(int id, string format)
{
var yourModel = ...;
if (string.IsNullOrWhiteSpace(format))
return View(yourModel);
return Ok(yourModel);
}
By using IActionResult as the return type, you can return either a ViewResult or an OkObjectResult. You can get access to the format value by taking it as a parameter in your action, check if it's empty and then react accordingly.
I also added Controller as the base class in order to access the convenience methods for creating the relevant results (View(...) and Ok(...)).
If you're going to be using this pattern a lot, to keep your controllers as clean as possible, you could create a base class that exposed a "FormatOrView" method:
[FormatFilter]
public abstract class FormatController : Controller
{
protected ActionResult FormatOrView(object model)
{
var filter = HttpContext.RequestServices.GetRequiredService<FormatFilter>();
if (filter.GetFormat(ControllerContext) == null)
{
return View(model);
}
else
{
return new ObjectResult(model);
}
}
}
And then your controller can inherit from this and use the FormatOrView method
public class ProductsController : FormatController
{
[Route("[controller]/[action]/{id}.{format?}")]
public ActionResult GetById(int id)
{
var product = new { Id = id };
return FormatOrView(product);
}
}
Edit to list final accepted answer by GreyCloud: Here is a generic slightly simplified method you can put into a controller (or make an extension method or put into an abstract base class as above). Note the ?. in case the service is not defined for some reason.
private ActionResult<T> FormatOrView<T>(T model) {
return HttpContext.RequestServices.GetRequiredService<FormatFilter>()?.GetFormat(ControllerContext) == null
? View(model)
: new ActionResult<T>(model);
}
The FormatFilter is part of the content negotiation of your app, in AspNetCore, you have the control to handle your input or output formatters also on the ConfigureServices where you have more control, even you can add more media types there
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options .OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
options .InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options ));
//more output formatters
var jsonOutputFormatter = options.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
if (jsonOutputFormatter != null)
{
jsonOutputFormatter.SupportedMediaTypes.Add("application/vnd.myvendormediatype");
}
}
}
But going back on the content negotiation in your controllers you can keep just one. The only thing is that you need to know the mediaType to return your View or your json content. Only be sure to pass an accept header with the content type you want. With the content type you are defining for an api or for an mvc application which is the content/format the client should expect
[HttpGet("[action]/{id}")]
public IActionResult public Product GetById(int id, [FromHeader(Name = "Accept")] string mediaType)
{
if (mediaType == "application/vnd.myvendormediatype")
{
var data = GetYourData(...)
return Json(data);
}
else return View("YourDefaultView");
}

How to read values from the querystring with ASP.NET Core?

I'm building one RESTful API using ASP.NET Core MVC and I want to use querystring parameters to specify filtering and paging on a resource that returns a collection.
In that case, I need to read the values passed in the querystring to filter and select the results to return.
I've already found out that inside the controller Get action accessing HttpContext.Request.Query returns one IQueryCollection.
The problem is that I don't know how it is used to retrieve the values. In truth, I thought the way to do was by using, for example
string page = HttpContext.Request.Query["page"]
The problem is that HttpContext.Request.Query["page"] doesn't return a string, but a StringValues.
Anyway, how does one use the IQueryCollection to actually read the querystring values?
You can use [FromQuery] to bind a particular model to the querystring:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding
e.g.
[HttpGet()]
public IActionResult Get([FromQuery(Name = "page")] string page)
{...}
You could use the ToString method on IQueryCollection which will return the desired value if a single page parameter is specified:
string page = HttpContext.Request.Query["page"].ToString();
if there are multiple values like ?page=1&page=2 then the result of the ToString call will be 1,2
But as #mike-g suggested in his answer you would better use model binding and not directly accessing the HttpContext.Request.Query object.
ASP.NET Core will automatically bind form values, route values and query strings by name. This means you can simply do this:
[HttpGet()]
public IActionResult Get(int page)
{ ... }
MVC will try to bind request data to the action parameters by name ... below is a list of the data sources in the order that model binding looks through them
Form values: These are form values that go in the HTTP request using the POST method. (including jQuery POST requests).
Route values: The set of route values provided by Routing
Query strings: The query string part of the URI.
Source: Model Binding in ASP.NET Core
FYI, you can also combine the automatic and explicit approaches:
[HttpGet()]
public IActionResult Get(int page
, [FromQuery(Name = "page-size")] int pageSize)
{ ... }
Here is a code sample I've used (with a .NET Core view):
#{
Microsoft.Extensions.Primitives.StringValues queryVal;
if (Context.Request.Query.TryGetValue("yourKey", out queryVal) &&
queryVal.FirstOrDefault() == "yourValue")
{
}
}
You can just create an object like this:
public class SomeQuery
{
public string SomeParameter { get; set; }
public int? SomeParameter2 { get; set; }
}
And then in controller just make something like that:
[HttpGet]
public IActionResult FindSomething([FromQuery] SomeQuery query)
{
// Your implementation goes here..
}
Even better, you can create API model from:
[HttpGet]
public IActionResult GetSomething([FromRoute] int someId, [FromQuery] SomeQuery query)
to:
[HttpGet]
public IActionResult GetSomething(ApiModel model)
public class ApiModel
{
[FromRoute]
public int SomeId { get; set; }
[FromQuery]
public string SomeParameter { get; set; }
[FromQuery]
public int? SomeParameter2 { get; set; }
}
StringValues is an array of strings. You can get your string value by providing an index, e.g. HttpContext.Request.Query["page"][0].
IQueryCollection has a TryGetValue() on it that returns a value with the given key. So, if you had a query parameter called someInt, you could use it like so:
var queryString = httpContext.Request.Query;
StringValues someInt;
queryString.TryGetValue("someInt", out someInt);
var daRealInt = int.Parse(someInt);
Notice that unless you have multiple parameters of the same name, the StringValues type is not an issue.
in .net core if you want to access querystring in our view use it like
#Context.Request.Query["yourKey"]
if we are in location where #Context is not avilable we can inject it like
#inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
#if (HttpContextAccessor.HttpContext.Request.Query.Keys.Contains("yourKey"))
{
<text>do something </text>
}
also for cookies
HttpContextAccessor.HttpContext.Request.Cookies["DeniedActions"]
Maybe it helps.
For get query string parameter in view
View:
#inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
#{ Context.Request.Query["uid"]}
Startup.cs ConfigureServices :
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
I have a better solution for this problem,
request is a member of abstract class ControllerBase
GetSearchParams() is an extension method created in bellow helper
class.
var searchparams = await Request.GetSearchParams();
I have created a static class with few extension methods
public static class HttpRequestExtension
{
public static async Task<SearchParams> GetSearchParams(this HttpRequest request)
{
var parameters = await request.TupledParameters();
try
{
for (var i = 0; i < parameters.Count; i++)
{
if (parameters[i].Item1 == "_count" && parameters[i].Item2 == "0")
{
parameters[i] = new Tuple<string, string>("_summary", "count");
}
}
var searchCommand = SearchParams.FromUriParamList(parameters);
return searchCommand;
}
catch (FormatException formatException)
{
throw new FhirException(formatException.Message, OperationOutcome.IssueType.Invalid, OperationOutcome.IssueSeverity.Fatal, HttpStatusCode.BadRequest);
}
}
public static async Task<List<Tuple<string, string>>> TupledParameters(this HttpRequest request)
{
var list = new List<Tuple<string, string>>();
var query = request.Query;
foreach (var pair in query)
{
list.Add(new Tuple<string, string>(pair.Key, pair.Value));
}
if (!request.HasFormContentType)
{
return list;
}
var getContent = await request.ReadFormAsync();
if (getContent == null)
{
return list;
}
foreach (var key in getContent.Keys)
{
if (!getContent.TryGetValue(key, out StringValues values))
{
continue;
}
foreach (var value in values)
{
list.Add(new Tuple<string, string>(key, value));
}
}
return list;
}
}
in this way you can easily access all your search parameters. I hope this will help many developers :)
Some of the comments mention this as well, but asp net core does all this work for you.
If you have a query string that matches the name it will be available in the controller.
https://myapi/some-endpoint/123?someQueryString=YayThisWorks
[HttpPost]
[Route("some-endpoint/{someValue}")]
public IActionResult SomeEndpointMethod(int someValue, string someQueryString)
{
Debug.WriteLine(someValue);
Debug.WriteLine(someQueryString);
return Ok();
}
Ouputs:
123
YayThisWorks
Startup.cs add this service
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Your view add inject #inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
get your value
Code
#inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
#{
var id = HttpContextAccessor.HttpContext.Request.RouteValues["id"];
if (id != null)
{
// parameter exist in your URL
}
}
In case you want to access QueryString inside of an asp.net core view you can do it like this:
#inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
#if (Context.Request.Query.Keys.Any())
{
<button>--ClearFilters--</button>
}
we usually can fetch data from routing in 3 way:
1.query string
2.query params
3.hybrid
I describe query string:
exp:
[HttpGet("Home/routing")]
public IActionResult privacy(String name)
{
return ViewModel:name
}
to pass name as querystring:
url:port/Home/routing?name=Alex

Alternate names for querystring parameters

Is it somehow possible to set alternate names for querystring parameters in ASP.NET MVC?
I have this simple controller Index action:
public ActionResult Index(color = "")
{
...
}
Calling http://mysite.com/mypage/?color=yellow works quite nicely, the color parameter automatically picks up its value "yellow" from the querystring.
But now I would like to have a localized variant of the same page, with “pretty” localized parameters, but still working with the same controller method. Example: http://mysite.com/mypage/?farve=gul. Here I would like “gul” to be passed in as the color parameter to the default Index() ation method.
How do I set mappings for alternate names for querystring parameters?
How do I set mappings for alternate names for querystring parameters?
You could write a custom model binder.
So as in every ASP.NET MVC application you start by writing a view model:
public class MyViewModel
{
public string Color { get; set; }
}
and then a model binder for this model:
public class MyViewModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var query = controllerContext.HttpContext.Request.QueryString;
var value = query["color"] ?? query["gul"] ?? query["couleur"];
return new MyViewModel
{
Color = value,
};
}
}
which will be registered at your Application_Start:
ModelBinders.Binders.Add(typeof(MyViewModel), new MyViewModelBinder());
and now your controller action may take the view model as parameter:
public ActionResult Index(MyViewModel model)
{
...
}
Of course you could make the model binder more flexible by using some custom attribute on the property:
public class MyViewModel
{
[PossibleQueries("color", "gul", "couleur")]
public string Color { get; set; }
}
and in the model binder read those values and try reading them from the query string until you find one that is not null.
How about a second controller with a localized/pretty name where the actions and parameters have localized names and call the actions from the default/english controller? With this method you have all parts of the url localized.
Controller mypage
{
ActionResult Index(string color)
{
// normal code
}
}
Controller meineseite
{
ActionResult Index(string farbe)
{
return mypage.Index(farbe);
}
}

Map post parameters to a model

I have a model I want to use for communication with an external web service. It's supposed to call a specific post action on my website.
public class ConfirmationModel{
...
public string TransactionNumber {get; set;}
}
public ActionResult Confirmation(ConfirmationModel){
...
}
The problem is the parameters names they pass are not very human-readable. And I want to map them to my more readable model.
't_numb' ====> 'TransactionNumber'
Can this be done automatically? With an attribute maybe? What's the best approach here?
Create a model binder:
using System.Web.Mvc;
using ModelBinder.Controllers;
public class ConfirmationModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = new ConfirmationModel();
var transactionNumberParam = bindingContext.ValueProvider.GetValue("t_numb");
if (transactionNumberParam != null)
model.TransactionNumber = transactionNumberParam.AttemptedValue;
return model;
}
}
Initialise it in Global.asax.cs:
protected void Application_Start()
{
ModelBinders.Binders.Add(typeof(ConfirmationModel), new ConfirmationModelBinder());
}
Then in your action method
[HttpPost]
public ActionResult Confirmation(ConfirmationModel viewModel)
You should see the value of t_numb appear in TransactionNumber property of the viewmodel.
Agree that model binder is better: here's an alternate idea though
public ActionResult Create(FormCollection values)
{
Recipe recipe = new Recipe();
recipe.Name = values["Name"];
// ...
return View();
}
and a good read about both: http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx

Categories