Permanent redirect across HTTP methods? - c#

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.

Related

What's the correct structure for a HTTP Post method? ASP.NET Core Web API

I'm working on a simple notes api, I'm trying to create a Put method to update a note in my notes list, but when I try to update any note through the SwaggerUI I get a the 404 status code. I think that I'm missing something in the structure.
This is my [HttpPut] request:
[HttpPut("{id}")]
public IActionResult Put([FromBody] Note requestParam)
{
if (!ModelState.IsValid)
{
return BadRequest("Not a valid model");
}
using (_datacontext)
{
var ExistingNote = _datacontext.Note.Where(n => n.Id == requestParam.Id)
.FirstOrDefault<Note>();
if (ExistingNote != null)
{
ExistingNote.Title = requestParam.Title;
ExistingNote.Description = requestParam.Description;
ExistingNote.Completed = requestParam.Completed;
_datacontext.SaveChanges();
} else
{
return NotFound();
}
}
return Ok();
}
My DataContext:
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> option) : base(option)
{
}
public DbSet<Note> Note { get; set; }
}
And lastly my Note Model:
public class Note
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public bool Completed { get; set; }
}
After looking for different examples I haven't found a standard approach so I'm not sure what to do about it
I've researched about Http bodies since it seemed like it needed to be part of the request but still get the error code. What could be wrong with it? (Both post and get methods work!).
Also, the error code:
When using the [HttpPut("{id}")] attribute on your controller, you need to add a parameter to the controller method's signature:
IActionResult Put([FromRoute] int Id, [FromBody] Note requestParam)
You can then call the API like this when Id=123
PUT http://{base-url}/123
Then you need to query the data context using the id from the route (which means you can remove it from the body)
On the other hand, if you don't want the Id as part of the request URL and keep it in the body, you need to remove the Id from the route template:
[HttpPut] without {id}.
Needless to say, make sure the Id actually exists in the data context. Otherwise your code will return, yes, a 404.

Model value redirected to another action does not pass all values

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)

Authorize attribute Roles from Database

I want to get the below roles(Admin,IT,..) from the database without hard coding on top of the action result. Please provide any help.
[Authorize(Roles = "Admin,IT")]
public ActionResult Index()
{
}
There aren't any super-easy ways to do this. You can apply the [Authorize] attribute to a controller instead of an action, but it is still "hard-coding" it.
You could create a custom Authorization attribute ([link])1, but you would have to store the Routing values in the database, as well as the Roles that were allowed to access the route. However this just shifts the burden of making manual changes into the database from the code.
I don't really think that this should really be considered "Hard Coding" as you have to declare your authorization somewhere, and you can still have different users with different permissions in different environments. Who else but the developer should know best which routes require which authorization? Would you want to break your access control because you changed the routing somewhere?
create an Action finter
public class ValidationPermission : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(System.Web.HttpContext.Current.Session["UserName"] == null)
System.Web.HttpContext.Current.Response.RedirectToRoute("Login");
else{
// code check CheckPermission
}
}
}
Action controller
[ValidationPermission(Action = ActionEnum.Read, Module = CModule)]
public ActionResult Index()
{
// something code
}
You can try with this way
public static Role {
public static string Admin ="Admin";
public static string IT ="IT";
}
[Authorize(Roles = Role.Admin,Role.IT)]
public ActionResult Index()
{
}

How to validate GET url parameters through ModelState with data annotation

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

How to route MVC so that I can post a form with a model.action parameter?

Hopefully this is an easy one for somebody out there.
I am trying to post a form to my MVC controller that happens to have an "action" property on the model.
Unfortunately, the model.action is resolving to the controller action, not the posted model's action property.
public class PostModel
{
public string action { get; set; }
public string username { get; set; }
public string password { get; set; }
}
public ActionResult DoSomething(string id, PostModel model)
{
// id == 98f4
// model.username == "TEST"
// model.password == "TEST"
// model.action == "DoSomething" NOT "TEST" as I was expecting.
}
Here is what I post:
POST -> http://localhost:7832/Forms/DoSomething/98f4?username=TEST&password=TEST&action=TEST
Please keep in mind I have no control over the form data being posted, so I cannot change the model's action property. I need to be able to address this problem on the MVC server side.
How do I overwrite the setting of the action property in my model to the acction of the controller? I would only need this functionality for one particular controller in my project.
Any suggestions?
Ok I figured out the problem. Actually it was with my use of fiddler2 to hit the controller action. When I had the action as part of the URL, MVC would replace with the controller action.
When I made the action part of the request.body, and added the "Content-Type: application/x-www-form-urlencoded" to the header, then the controller model had the expected values.

Categories