I have a Web API project... I would like to respect the REST principles, so I should have just a GET method and just a POST method...
I have to do a search, so i think this matches the GET method, because after the search I obtain the result and I show it in the page... If I do not find anything I must create the object... this action is a POST...
Now I have a problem... I must validate the filters of the search, because filters are a tax code and a alpha-numeric code (6 chars)... I have already done a client side validation. Now I should do a Server Side validation.
Untill now, we have used data annotation to validate the request, but this is a GET... so my method has this signature:
[HttpGet]
public IHttpActionResult GetActivationStatus(string taxCode, string requestCode)
{
if (ModelState.IsValid)
{
...
}
}
But how can I validate my ModelState with Data Annotation?
Thank you
Create your own model...
public class YourModel
{
[//DataAnnotation ...]
public string taxCode { get; set; }
[//DataAnnotation ...]
public string requestCode { get; set; }
}
And change your signature of your server side controller:
[HttpGet]
public IHttpActionResult GetActivationStatus([FromUri] YourModel yourmodel)
{
if (ModelState.IsValid)
{
...
}
}
If your client side code already worked you don't have to change it... Please, note that the properties of your Model are the same of the parameter you are passing now (string taxCode, string requestCode)... and they are case sensitive...
EDIT:
I mean that you can call your controller in this way:
http://localhost/api/values/?taxCode=xxxx&requestCode=yyyy
Related
I have an ASP.Net Core application which needs passing of a model from one action to another.
These are models :
public class ClassA
{
public string Id{get;set;}
public string Name {get;set;}
public StudentMarks Marks {get;set;}
}
public class StudentMarks
{
public int Marks {get;set;}
public string Grade {get;set;}
}
And the post Controller:
[HttpPost]
public ActionResult TestAction1(ClassA model)
{
return RedirectToAction("TestAction2", model);
}
public ActionResult TestAction2(ClassA model)
{
}
In TestAction 1 while debugging, i see that Id, Name and marks have value.
I am getting the value for Id in TestAction2 same as that in TestAction1. However the value of complex object Marks is not obtained in the TestAction2 action method.
What are my other options?
You cannot redirect with a model. A redirect is simply an empty response with a 301, 302, or 307 status code, and a Location response header. That Location header contains the the URL you'd like to redirect the client to.
The client then must make a new request to that URL in the header, if it so chooses. Browsers will do this automatically, but not all HTTP clients will. Importantly, this new request is made via a GET, and GET requests do not have bodies. (Technically, the HTTP spec allows for it, but no browser or HTTP client out there actually supports that.)
It's unclear what your ultimate goal is here, but if you need to persist data temporarily between requests (such as a redirect), then you should serialize that data into a TempData key.
You can use TempData to pass model data to a redirect request in Asp.Net Core In Asp.Net core, you cannot pass complex types in TempData. You can pass simple types like string, int, Guid etc. If you want to pass a complex type object via TempData, you have can serialize your object to a string and pass that. I have made a simple test application that will suffice to your needs:
Controller:
public ActionResult TestAction1(ClassA model)
{
model.Id = "1";
model.Name = "test";
model.Marks.Grade = "A";
model.Marks.Marks = 100;
var complexObj = JsonConvert.SerializeObject(model);
TempData["newuser"] = complexObj;
return RedirectToAction("TestAction2");
}
public ActionResult TestAction2()
{
if (TempData["newuser"] is string complexObj )
{
var getModel= JsonConvert.DeserializeObject<ClassA>(complexObj);
}
return View();
}
Model:
public class ClassA
{
public ClassA()
{
Marks = new StudentMarks();
}
public string Id { get; set; }
public string Name { get; set; }
public StudentMarks Marks { get; set; }
}
public class StudentMarks
{
public int Marks { get; set; }
public string Grade { get; set; }
}
If you want to persist your TempData values for more requests you can use Peek and Keep functions. This answer can give more insight on these functions.
I think you're getting model and routeValues mixed up. The overload of RedirectToAction that you're calling (takes a string and an object) expects a routeValues argument, not a model argument. https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.redirecttoaction?view=aspnetcore-2.2
TestAction1 is called via Post, but TestAction2 is called via Get. You need to work out a URL that will let you call TestAction2 the way you want (independently of the RedirectToAction in TestAction1). I'm guessing this will involve setting up a custom route. Once you have a URL that will let you call TestAction2 the way you want, you can specify the route values to form that URL in the RedirectToAction in TestAction1.
I think the problem is that you shuold use:
return RedirectToAction("TestAction2", model);
(you did this without return)
I was wondering if it's possible to have something like:
[HttpGet]
public JsonResult AddTextFile(string path)
{
if(string.IsNullOrEmpty(path))
{
// return error
}
}
But in the case where I might have a lot of parameters in my controller method I don't want to use string.IsNullOrEmpty() for each one. I know that I could use view-models with a [Required] field indicator and that will allow me to use ModelState, but because these are all API endpoints, I'm requiring information through get parameters.
Is there an elegant way of requiring controller method parameters, so that if any of them are not set it would return a generic response message?
Use a complex object as the parameter:
[HttpGet]
public JsonResult AddTextFile(MyObject obj) {
if(!ModelState.IsValid) {
// return error
}
}
public class MyObject {
[Required]
public string Path { get; set; }
}
The properties of MyObject will be taken from the query parameters, like: /addtextfile?path=blah
And the model validation will apply.
Looking for best practices when working with nested routes in .NET Core MVC.
Let's say CampusController.cs works with a base model:
[Route("api/campus/")]
public class CampusController : Controller
{
...
[HttpGet]
[Route("{campusId}")]
public IActionResult GetCampusInfo ([FromQuery]int campusId) { ... }
}
And BuildingController.cs works with a child model:
[Route("api/campus/{campusId}/building")]
public class BuildingController : Controller
{
...
[HttpGet]
[Route("{buildingId}")]
public IActionResult GetBuilding ([FromQuery]int buildingId) { ... }
[Route("{buildingId}/")]
public IActionResult GetBuilding ([FromQuery]int buildingId) { ... }
....
(more Action Methods)
}
If buildingId maps directly to the database it could retrieved even if the provided campusId isn't the parent. To keep the URL clean when calling /api/campus/{campusId}/building/{buildingId} I'd like to validate {campusId} and return a 4xx coded IActionResult if it's invalid. There has to be a better way than including validation logic in every Action Method inside BuildingController.
Is there a way to cascade multiple Action methods on different controllers? So that a validation method on CampusController would be called first and in turn call a method onBuildingController?
Is there a way to have a controller-level verification of campusId that could short circuit and return a ActionResult if validation fails?
EDIT: When I refer to validation logic I mean API signals; not the business-logic that actually determines if campusId is/isn't valid.
Thanks in advance!
If using placeholder in the route prefix you would also need to include it in the action itself
[Route("api/campus/{campusId:int}/building")]
public class BuildingController : Controller {
//...
[HttpGet]
[Route("{buildingId:int}")] // Matches GET api/campus/123/building/456
public IActionResult GetBuilding ([FromRoute]int campusId, [FromRoute]int buildingId) {
//... validate campus id along with building id
}
}
If concerned about repeated code for validation then create a base controller for campus related request and have a shared validation method.
Another option is to have a service/repository that can be used to verify campus id and its relation to the provided building id if needed.
It sounds like you want your users to provide a campusId when talking to the BuildingController, and your BuildingController to validate campusId in a DRY kind of way.
If that's the case, you can create an input model for your BuildingController methods:
public class BuildingIdInput
{
[Required]
public int? CampusId { get; set; }
[Required]
public int? BuildingId { get; set; }
}
Then you can let MVC bind user input to this model.
[Route("api/campus")]
public class BuildingController : Controller
{
[HttpGet]
[Route("{campusId}/building/{buildingId}")]
public IActionResult GetBuilding (BuildingIdInput input)
{
if (ModelState.IsValid)
{...}
}
}
I've written a base class and some classes which derive from it.
I want to use these classes in one ActionResult, but if I'm trying to cast PSBase to PS1 I'm getting a System.InvalidCastException that type PSBase can not be converted to PS1.
Classes:
public class PSBase
{
public int stationId { get; set; }
public string name { get; set; }
}
public class PS1 : PSBase
{
public string reference { get; set; }
}
public class PS2 : PSBase
{
}
ActionResult:
[HttpPost]
public ActionResult ProductionStep(PSBase ps)
{
if (ModelState.IsValid)
{
var product = db.Product.FirstOrDefault(.........);
switch (ps.stationId )
{
case 1:
{
product.Reference = ((PS1)ps).reference;
db.SaveChanges();
break;
}
}
}
return View();
}
As I don't want to have for each class a own ActionResult (repeating much of the same code many times) I wanted put all this to one ActionResult. Any Ideas how I could implement this?
What you are trying to do will never work without custom ModelBinder (and even then it will be a huge mess I'd not recommend to implement), sorry.
Only when you are passing a model from Controller to View it remembers what type it was originally (including inheritance, etc.) because at that point you are still on the server side of the page and you are merely passing an object.
Once you enter a view and submit a form all that does it creates some POST request with body containing list of values based on input names.
In your case if you have a form based on PS1 and used all the fields as inputs, you would get something like:
POST:
stationId = some value
name = some value
reference = some value
(there is no mention of the original type, controller, method, etc.)
Now, what MVC does is that it checks what argument you are using in the header of the method (in your case ProductionStep(PSBase ps)).
Based on the argument it calls a model binder. What the default model binder does is that it creates new instance of that class (in your case PSBase) and goes via reflection through all the properties of that class and tries to get them from the POST body.
If there are some extra values in the POST body those are forgotten.
Unless you write a custom model binder for this default MVC implementation can't help you there.
I'd recommend creating two separate methods, one of each accepting different implementation of PSBase.
If you want to read more on Model Binders check this out http://msdn.microsoft.com/en-us/magazine/hh781022.aspx
EDIT:
By creating two separate methods I mean something like this:
[HttpPost]
public ActionResult ProductionStepA(PS1 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
[HttpPost]
public ActionResult ProductionStepB(PS2 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
then you have to distinguish between them in the view via different form action.
I have the following couple of methods in my CommentsController.
[HttpGet]
[Authorize]
public ActionResult New(long id)
{
return RedirectToAction("Details", "Posts", new { id }); // lets be graceful.
}
[HttpPost]
[Authorize]
public ActionResult New(long id, string comment, IMiniPrincipal principal)
{
throw new NotImplementedException();
}
Both are resolved through any posts/{id}/comment route where id is a numeric value. I added the GET action mostly to avoid confusion (instead of just telling the user it doesn't exist when they attempt to access the route manually instead of through a form POST, I redirect them to the post the comment would've been submitted to).
The question is whether I can use a Permanent Redirect result in HTTP GET requests and still not get permanently redirected during HTTP POST requests?
You might be better off having your POST handler map to a model, which should make it much less likely that the same URL maps to both actions.
So your code might change to something like:
[HttpPost]
[Authorize]
public ActionResult New(CommentModel model)
{
// { ...code... }
}
With the model looking something like:
public class CommentModel
{
public long ID { get; set; }
public string Comment { get; set; }
public IMiniPrincipal Principal { get; set; }
}
Permanent redirect works on a per-method basis, so you can actually perform a permanent redirect on a POST to a given url, while serving content on GET requests to the same url.