I have a Request class as follows:
public class GetCareTypesRequest
{
/// <summary>Tpa Partition</summary>
[Required]
public string TpaPartition { get; set; }
}
I am passing entire object as a single query parameter instead of individual in Controller method as follows:
public async Task<IHttpActionResult> Get([FromUri] GetCareTypesRequest request) {}
When I go to Swagger page, I am seeing "request" as a prefix for TpaPartition parameter:
Even Request URI is coming as: http://localhost:52354/api/CareTypes?request.tpaPartition=0000010000010001
I am expecting it to be like: http://localhost:52354/api/CareTypes?tpaPartition=0000010000010001 and I still want to send entire object as a single query parameter.
What should I do to achieve this? I came across few articles which suggest to create a custom ModelBinder but any easy way would be very helpful.
Related
I want to use global Model Validation filter for all my controllers in ASP.NET 5 application.But I faced with a problem that default binder doesn't fill model values from URI (but works fine for bodied POST actions). For example, I have controller with action:
public class TestController : ApiController
{
[HttpGet, Route("test/{id}"/return)]
public int TestAction([FromUri] TestModel model)
{
return model.Id;
}
public class TestModel
{
[Required]
public int? Id { get; set; }
}
}
Requesting this controller by URI, for example, localhost:12345/test/10/return, returning 'null' response. The same for another complex models, accessing some of [Required]-marked fields throws a NRE, like binder ignores {id} expression in route.
Any ideas where this behaviour can be turned off?
Sorry for your attention, seems to be there was something like typo or error in property names.
I tried to re-implement test actions with complex models as arguments for methods and now everything works as expected.
I am working with a BaseController that is used for a variety of entities. They may have int or string primary keys, represented by <TPk>.
E.g.:
[HttpGet]
public ActionResult Create(TPk id)
{
return View();
}
Everything is fine until I try and use TPk as an optional parameter.
[HttpGet]
public ActionResult Create(TPk id = default(TPk))
{
return View();
}
It seems that the 'optional' part isn't working.
So /controller/create/2 is fine, but /controller/create gives me the following error:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Create(Int32)'
The optional works fine with an int or string id. I can call /controller/create/2 AND /controller/create.
But using a generic type argument TPk, the parameterless route no longer works.
What I've Tried
I have tried making the TPk parameter nullable, but it won't compile:
The type 'TPk' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'Nullable'
I have tried changing the parameter name from id to altId as per this question - no joy
I have tried calling the same method, in exactly the same way, but with non-generic parameters. E.g.:
public virtual async Task<ActionResult> Create(int id = default(int))
This worked fine.
I have tried creating a simple new project to isolate this code. (Shown below). This still gives problems with the parameterless version.
Simple Code Test
Controller
public abstract class BaseController<TPk> : Controller
{
public ActionResult Create(TPk id = default(TPk))
{
return View();
}
}
public class NewsController : BaseController<int>
{
}
Entity Classes
public class BaseDataModel<TPk>
{
public TPk Id { get; set; }
public string Title { get; set; }
}
public class PageDataModel : BaseDataModel<string>
{
public string Content { get; set; }
}
public class NewsDataModel : BaseDataModel<int>
{
public DateTime Date { get; set; }
}
Asp.net conventions are heavily based on reflection. So this might explain the behavior. I have not tested if it realy does not work, but I am sure at this state you already tried to create a new project (POC) to preclude any custom code.
Maybe it can be fixed by looking deeper into the routing (method selection) and ModelBinder source code...
I would just create a different DuplicateRecord action instead.
If you do not understand your method without this comment, it is a good indication, that your current code probably smells anyway. (You are doing to much at the same thing):
// duplicates existing record if id is passed in, otherwise from scratch
Extract the shared things to another method (maybe even a service class) and have for each difference a seperate method.
That said, the idea of a generic CrudController is lovely, I tried this myself some years ago. But in trying so I have introduced all sort of generic parameters, strategy patterns, event delegates to make all possibilities possible.
What happens if you need a join?
What happens if you need a transaction?
How do you handle errors?
What happens if your crud logic needs 1, 2, 3 ... additional parameters to decide what to do?
Soft Delete / Hard Delete?
Cascade Delete / Restrict Delete?
What happens if you ...
I have written so much code, it was blessing to revert to the good old non generic code. And if abstracted away in a service, the ActionMethods realy do not need to get big.
public async Task<IActionResult> CreateProduct(CancellationToken ct, ProductCreateModel model)
{
var result = await _productService.CreateAsync(model, ct);
//create response with some helpers... probably some ActionFilters
}
Generics can work ofcorse in a simple crud mapping where each View has exact one Entity, but it does not scale very well. So beaware and think twice about what you realy want ;)
I'm looking for a proper way to handle empty parameters in a query string. Web Api does not accept query strings as "?id=1&category=", which seems reasonable, but I need to handle this case.
The quick and dirty solution is to use a custom value which will be interpreted on the server side (say "(empty)" for example) but I'm not satisfied with it...
Any suggestion ?
Thanks.
One way I have dealt with this in the past is to make a class to hold your paramaters and then use to ModelBinder attribute to bind your query parameters to the class properties.
So your class would look something like this:
public class QueryParams
{
public string Category {get; set;}
public int Id {get; set;}
}
And the method in your api controller would look like this:
public objectToReturn Get([ModelBinder] QueryParams)
{
//code here
}
This way if one of the parameters in the query string has no value it will simply be ignored.
You can use this attribute to achieve what you want.
[DisplayFormat(ConvertEmptyStringToNull = false)]
If "category" is missing then it will be null.
Otherwise, if "category=" or "category= " then will be an empty string or whitespace.
I am currently implementing a Web API project in .NET and I use the standard RESTful paths to perform CRUD operations on my Models via a Controller. For the sake of the question, let's assume I have a "Product" model.
When I call GET .../api/product/5 for example then I am successfully able to return the specific product, rendered as XML, to the requester by simply returning the relevant Product object in the GetMessage method in the controller as per convention. Serialization of the object happens "automagically".
Now, this works fine if I am simply rendering the properties for the Product object. But now, I have a method defined in Product that does some calculations. I also want to return this value in the XML. (To the receiving end it will appear just as another field - the receiver will not know that this is a calculated field rather than a property field read from the database) How do I get the value as calculated in the method to also be included in the response XML?
How do I get the value as calculated in the method to also be included in the response XML?
Instead of returning a Product instance from your method design a view model:
public class ProductViewModel : Product
{
public string SomeCalculatedField { get; set; }
}
and then return this view model from the action after setting the calculated field.
As an alternative from deriving from the Product class you could have it as property:
public class ProductViewModel
{
public Product Product { get; set; }
public string SomeCalculatedField { get; set; }
}
What's the benefit of setting an alias for an action method using the "ActionName" attribute? I really don't see much benefit of it, in providing the user the option to call an action method with some other name. After specifying the alias, the user is able to call the action method only using the alias. But if that is required then why doesn't the user change the name of the action method rather then specifying an alias for it?
I would really appreciate if anyone can provide me an example of the use of "ActionName" in a scenario where it can provide great benefit or it is best to use.
It allows you to start your action with a number or include any character that .net does not allow in an identifier. - The most common reason is it allows you have two Actions with the same signature (see the GET/POST Delete actions of any scaffolded controller)
For example: you could allow dashes within your url action name http://example.com/products/create-product vs http://example.com/products/createproduct or http://example.com/products/create_product.
public class ProductsController {
[ActionName("create-product")]
public ActionResult CreateProduct() {
return View();
}
}
It is also useful if you have two Actions with the same signature that should have the same url.
A simple example:
public ActionResult SomeAction()
{
...
}
[ActionName("SomeAction")]
[HttpPost]
public ActionResult SomeActionPost()
{
...
}
I use it when the user downloads a report so that they can open their csv file directly into Excel easily.
[ActionName("GetCSV.csv")]
public ActionResult GetCSV(){
string csv = CreateCSV();
return new ContentResult() { Content = csv, ContentEncoding = System.Text.Encoding.UTF8, ContentType = "text/csv" };
}
Try this code:
public class ProductsController
{
[ActionName("create-product")]
public ActionResult CreateProduct()
{
return View("CreateProduct");
}
}
It is also helpful when you need to implement method overloading.
public ActionResult ActorView()
{
return View(actorsList);
}
[ActionName("ActorViewOverload")]
public ActionResult ActorView(int id)
{
return RedirectToAction("ActorView","Home");
}
`
Here one ActorView accepts no parameters and the other accepts int.
The first method used for viewing actor list and the other one is used for showing the same actor list after deleting an item with ID as 'id'.
You can use action name as 'ActorViewOverload' whereever you need method overloading.
This class represents an attribute that is used for the name of an action. It also allows developers to use a different action name than the method name.