Accessing ViewModel properties in ASP.NET Core controller - c#

I've been following an EF core tutorial on basic CRUD operations, and am having trouble with getting data from my ViewModel.
I'm following their recommended practice of using TryUpdateModel (or in my specific case, TryUpdateModelAsync) in the following action.
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int id)
{
var versionToUpdate = await _context.Versions.FirstOrDefaultAsync(a => a.Id == id);
var editModel = new VersionEditModel() { Version = versionToUpdate };
if (await TryUpdateModelAsync(
editModel.Version,
"Version",
a => a.Name,
...further code removed for brevity
In the past, I would have passed the model itself in action signature, i.e.
public async Task<IActionResult> EditPost(VersionEditModel editModel)
But the recommendation in the tutorial was to use just the ID, fetch the item from the database and run TryUpdateModel against it.
This all works, but I want to access a value that has been POSTed to the action as part of the ViewModel, but I can't seem to access it.
If I add the model to the signature as an additional parameter, like this
public async Task<IActionResult> EditPost(int id, VersionEditModel editModel)
I can access it using editModel, but the TryUpdateAsync no longer works properly.
I assume there must be a way to get to this data, but I can't figure out the right way to get to it.
My main problem has been trying to find the right words to explain my issue and what I want, so please ask for clarification if something doesn't make sense.

Related

Why is MVC always going to [HttpPost] method?

I have a link which uses a query string, and it's always going to the [HttpPost] method instead of the [HttpGet] method.
The error I'm getting is a NullReferenceException on TempData["surveytype"], so I know it's going to Post instead of Get. I have no idea why though.
I found a couple of similar questions here but nothing that resolved my problem. I thought maybe MVC was interpreting this as a form submission and sending to HttpPost because I'm styling the link as a "btn btn-primary" class, but removing that changed nothing.
My link:
Start Response
Controller:
[HttpGet]
public ActionResult Create(int SurveyId)
{
TempData["SurveyId"] = SurveyId;
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = ...)] Response response)
{
if (ModelState.IsValid)
{
response.Userid = User.Identity.GetUserId();
response.Id = Guid.NewGuid();
db.response.Add(response);
db.SaveChanges();
TempData["ResponseId"] = response.Id;
int? surveyid = response.SurveyId;
var surveytype = db.surveys.Find(surveyid).surveytype;
TempData["surveytype"] = surveytype;
...
}
}
I think your [HttpGet] makes the routing to confuse - Remove it
public ActionResult Create(int SurveyId)
{
TempData["SurveyId"] = SurveyId;
return View();
}
Now based on the action name the route will take care either it's a get or post - just a solution try it out and let me know if you face same issue
Thanks - Happy coding :)
For starters, you shouldn't manually create links like that. It's very error prone, and you run into situations like this often. You should use the helpers to create the link. For example:
#Html.ActionLink("Start Responses", "Create", "Controllername", new { SurveyId = item.id }, new { #class = "btn btn-primary" })
Next, you should make sure you don't have any custom routing that might be interfering, either with Attribute based routing or by using MapRoute.
The biggest reason to use the helpers is that MVC can access pages from different actual url's, and hard coding a path like that is almost impossible to get right.
For instance, let's say you can access a page at http://exmample.com, http://example.com/Home or http://example.com/Home/Index.
Using a hard coded "../whatever" means that this will translate to the corresponding url's http://example.com/../whatever (obviously not what you want), http://example.com/Home/../Whatever (might be what you want, might not), or http://example.com/Home/Index/../Whatever (this is probably what you want want, but it won't get there unless the users's browser url has this third url to access it).
If you must hard code a link, then you should always use a full root relative url link ("/Home/whatever") instead of relative ("../whatever"), but even that has issues.. what happens if you decide to move your site into a subdirectory of your site, now all your hard coded urls are wrong.
Using Url helpers is always the best way to go in MVC.

Web API Routing - Overloaded GET method causes 405 Method Not Allowed for other verbs

I have an API controller that supports DELETE, GET, POST and PUT operations for an user object. The GET-one operation can retrieve a user account by their ID. But I also want to get the user by their UserName. I created separate methods for each and it seems to work fine as I can get the expected data both ways.
// GET: api/UserAccounts/5
[HttpGet]
[ResponseType(typeof(UserAccount))]
[Route("~/api/UserAccounts/{id:int}")]
public async Task<IHttpActionResult> GetUserAccount(int id) { ... }
// GET: api/UserAccounts/JohnDoe
[HttpGet]
[ResponseType(typeof(UserAccount))]
[Route("~/api/UserAccounts/{userName}")]
public async Task<IHttpActionResult> GetUserAccount(string userName) { ... }
// DELETE: api/UseAccounts/5
[HttpDelete]
[ResponseType(typeof(UserAccount))]
public async Task<IHttpActionResult> DeleteUserAccount(int id) { ... }
// GET: api/UserAccounts
[HttpGet]
public IQueryable<UserAccount> GetUserAccounts() { ... }
// POST: api/UserAccounts
[HttpPost]
[ResponseType(typeof(UserAccount))]
public async Task<IHttpActionResult> PostUserAccount(UserAccount userAccount) { ... }
// PUT: api/UserAccounts/5
[HttpPut]
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutUserAccount(int id, UserAccount userAccount)
The problem is that whenever I attempt to do a DELETE, POST or PUT operation, I get a 405 - Method Not allowed response. If I comment out the GetUser(string) method, they all work.
I looked through a bunch of articles and documentation and I saw something about using the RouteOrder property on the Route attribute, but that doesn't make any difference.
My WebApiConfig has this code:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Since I am using attribute routing for my GetUser methods I would think this should be working. I'm using the default routes for the other methods/verbs.
Surely there is a way to make this work, right?
UPDATE:
I'm still experiencing this issue. I removed the GET (string) operation and removed all the attribute routes since the other operations use the DefaultApi route. All the standard operations work. However, if I introduce a GET (string) operation, only the GET-all operation works. The GET (int) version returns no response, presumably because the API cannot determine which GET overload to use. All other operations return 405 Method Not Allowed. To get around this I added an attribute route to the GET (int) and GET (string) methods and those now work, but the DELETE, POST and PUT operations return 405 Method Not Allowed.
If I add attribute routes to all the methods, everything works except the POST operation. In that case I get 405 Method Not Allowed. Here are the methods signatures with the attribute routes:
[HttpDelete]
[ResponseType(typeof(UserAccount))]
[Route("~/api/UserAccounts/{id:int}")]
public async Task<IHttpActionResult> DeleteUserAccount(int id)
[HttpGet]
[ResponseType(typeof(UserAccount))]
[Route("~/api/UserAccounts/{id:int}")]
public async Task<IHttpActionResult> GetUserAccount(int id)
[HttpGet]
[ResponseType(typeof(EnrollmentApiAccountDto))]
[Route("~/api/UserAccounts/{userName}")]
public async Task<IHttpActionResult> GetUserAccount(string userName)
[HttpGet]
[Route("~/api/UserAccounts")]
public IQueryable<UserAccount> GetUserAccounts()
[HttpPost]
[ResponseType(typeof(UserAccount))]
[Route("~/api/UserAccounts")]
public async Task<IHttpActionResult> PostUserAccount(UserAccount userAccount)
[HttpPut]
[ResponseType(typeof(void))]
[Route("~/api/UserAccounts/{id:int}")]
public async Task<IHttpActionResult> PutUserAccount(int id, UserAccount userAccount)
There is no constraint on the GET (string) attribute route. I can't use {username:alpha} because the username might contain digits.
I need to support the GET by userName operation because I need it for user authentication. But unless I can figure this out, I may have to create a different controller just to support that action. I prefer not to.
I didn't mention this before, but there are two clients using this API controller.
The first is an ASP.NET web forms application. With all the methods decorated with route attributes, all operations work as expected. In the case of the POST operation, the route it is posting to is /api/UserAccounts. This is what I expected as the ID argument is not necessary.
The second is an AngularJS application. The way the resource is configured, the ID parameter is sent for POST's, meaning the route being posted to is /api/UserAccounts/0. This seems to be the problem. (Oddly a GET request doesn't pass the ID when I want to get all records, only when I want one record.)
I noticed this using Fiddler to inspect the request/response. For some reason, Fiddler was not able to capture the traffic between the web forms application and the API, but I know the code is composing the POST request without the ID parameter because it is not necessary.
Therefore, I then used Fiddler to compose a new POST request to /api/UserAccounts using the same payload that failed before, and when I submitted it, the PostUserAccount method controller executed and created the database record. This is what I expected.
However it must be noted that I have many other controllers that are called by my AngularJS application using this pattern of route (appending the ID of 0 for POST requests) and there is no problem. Apparently the addition of an overload for the GET operation that takes a string argument messes this up. I now need to figure out how to get my AngularJS application to not pass the ID argument for POST methods. I'll create another question to deal with that issue.

Best practice: User Identites as parameters?

I am currently developing an API with ASP.NET in C#.
One endpoint needs to call another in order to return a value.
public class Testcontroller : BaseApiController
{
[Authorize]
[HttpGet]
[Route("1", Name = "F1")]
public async Task<IHttpActionResult> F1()
{
return await F2(); // calls 2nd method
}
[Authorize]
[HttpGet]
[Route("2", Name = "F2")]
public async Task<IHttpActionResult> F2()
{
int I = 2;
return Ok(I.ToString());
}
}
Ok, the returned value of this whole thing will be 2, which is absolutely fine. However, in the real API, the second methods needs to retrieve some data about the user. That is normally handled with
var Name = ClaimsPrincipal.Current.Identity.Name;
var CurrentUser = await this.AppUserManager.FindByNameAsync(Name);
These two lines get the user information through the bearer token, that is passed to the method through the authorization process.
Considering this, the first function could call the other one. The downside is that those two LOCs for the user data do not work because this token is not passed properly.
How would you suggest working around this problem? I thought about adding an optional parameter and passing the CurrentUser through that. But I thought that might cause some issues in terms of security?
Thanks for your help!
Firstly don't use ClaimsPrincipal.Current. In Controllers you have a User property, that's where the identity is. ClaimsPrincipal.Current is a hang over from .NET 3.5. If you are going to move to .NET Core, then the User property is the right way to do it.
In response to your actual question there's no security issues in passing that through to other functions outside your controller.

Update object from the view inside the controller

I'm trying to make changes to an object that my controller is getting back from the view. When I debug and step through the program and look at the values of job.JobTypesId and job.JobStatusID the correct values are displaying but they are not being saved while everything else does. I have read a couple of articles that explain that the framework needs to know that the model has changed and that's what the entity state is supposed to do (http://www.mattburkedev.com/saving-changes-with-entity-framework-6-in-asp-dot-net-mvc-5/). I have also tried to use UpdateModel but that dosent seem to work either (http://forums.asp.net/t/1807184.aspx?MVC+4+Not+saving+data+after+edit). I get a message "The model of type 'StaffServiceTracker.Models.Job' could not be updated.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Job job) {
var task = from t in db.JobTypes where t.JobTypeID == job.JobTypesID.JobTypeID select t;
var status = from s in db.Statuses
where s.StatusID == job.JobStatusID.StatusID
select s;
job.JobTypesID = task.FirstOrDefault();
job.JobStatusID = status.FirstOrDefault();
db.Entry(job).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
Any guidance would be appreciated.
First of all, you should use ViewModels (in 90% of the cases) instead of business objects in the views. This is because you would send only the necessary data to the client, you can combine business objects, etc.
Second, once you receive the ViewModel in the controller you should first validate it (always validate at client and server side because client side validations can be bypassed).
Once you have validated everything, get the Job object from the database using EF and set the properties. Call SaveChanges and that's it. It would be better if you move this code from the controller to the service layer.
One more thing: you can use SQL Server Profiler to know exactly what queries EF is executing. That way you'll understand what's happening.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(JobVM jobVM) {
// validate VM
// ...
// the following code should be in the Service Layer but I'll write it below to simplify
// get job from DB
var job = db.Jobs.SingleOrDefault(p => p.Id == jobVM.Id);
job.JobTypesID = jobVM.JobTypesID;
job.JobStatusID = jobVM.StatusId;
db.SaveChanges();
return RedirectToAction("Index");
}

Is it safe to use query string in a post action?

I've got two controller actions at the moment: one that displays a strongly typed view, based on an unique identifier and another one that changes the model. Here's some code to visualize what I mean:
[HttpGet]
[ActionName("Edit")]
public ActionResult UpdateDevice(string code)
{
// Request the device based on the code.
var device = GetDeviceModel(code);
// Present the device in a view.
return View(device);
}
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
}
The code identifies the device, but it is also possible to change it. And that's my problem: Inside the post method I can access the new code using model.Code, but I also need to know the old code, to be able to change it.
I tried several alternatives, but none of them did satisfy my requirements:
ViewData does not get persisted until the post.
TempData is based on Sessions or Cookies – none of them I want to use at the moment.
Hidden fields and model bindings are not an option, because they can be manipulated on client side.
Finally I tried requesting data from the query string like this:
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
var oldCode = Request.QueryString["code"];
}
And this works! But I haven't found any resources on this around the web. So my question is: Is using the query string inside a post action safe against modifications? And what (if there are any) are the conditions for this to work?
If this is not a valid way to "remember" the code, are there any alternatives?
Based on the requirements you have mentioned in your question, it seems that you are looking for safety in the case. So I would say that QueryString is not safe. In my opinion using Session variables is the appropriate method that keeps your critical data in a safe location. You can use the method like this
[HttpGet]
[ActionName("Edit")]
public ActionResult UpdateDevice(string code)
{
Session["code"] = code;
....
}
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
if (Session["code"] == null)
throw new Exception("Error Message.");
var code = Session["code"].ToString();
....
Session["code"] = null;
}
I think you can make another property inside DeviceModel name OldCode.
and in your view you can save this value in hiddenfield like this
#Html.HiddenFor(m=>m.OldCode)
Now in controller post method you can get both the values like this.
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
var oldcode=model.OldCode;
var newcode=model.Code;
}
Hope this helps...
Nothing submitted via a GET or a POST request is safe from modifications. While a GET query string is obviously easy to modify, it doesn't take much effort to spoof POST variables either. It sounds to me like you need to re-think your approach.
Optimally, you would do permission checking server-side to determine if the user is allowed to update the device with the passed (old) code. If they do not have permission, return an error such as an HTTP 550 code.
If you truly can't support this approach, I would suggest adding an "OldCode" field to the DeviceModel class as suggested by others, but encrypting it before sending it to the client. Then you can safely write it to a hidden field, and decrypt back on the server without fear of the user changing the value.

Categories