views:

255

answers:

3

In my ASP.NET MVC2 application, I have a ViewModel class called UserCreateViewModel.

In this class, there are a number of properties that directly map to a LINQ-to-SQL class, called User. I'm using AutoMapper to perform this mapping and it works fine.

In my Create action of the UserController, I receive a partially complete UserCreateViewModel, that contains information regarding OpenId authentication.

this is the definition of UserCreateViewModel:

public class UserCreateViewModel
{
    public string OpenIdClaimedIdentifier { get; set; }
    public string OpenIdFriendlyIdentifier { get; set; }
    public string Displayname { get; set; }
    public string Email { get; set; }
    public string PhoneNumber { get; set; }
}

In the Create view, I do no wish for the OpenIdClaimedIdentifier or the OpenIdFriendlyIdentifier to be editable.

I've used a strongly typed create view (using the built in auto create), but this provides me with editable textbox's for these two properties. If I remove the specific html completely, when the create form is return (and is returned directly to a UserCreateViewModel):

 [AcceptVerbs(HttpVerbs.Post)]
 public ActionResult Create(UserCreateViewModel viewModel, string ReturnUrl)

the returned viewModel doesn't contain values for OpenIdClaimedIdentifier and OpenIdFriendlyIdentifier.

I have investigated the use of the [HiddenInput] attribute but I couldn't seem to make this work. I also have tried using a hidden <input/> tag in the form, which works, but this seems a bit clunky.

Is there a better way to do this? or is using a hidden <input> the only way?

EDIT: To clarify the logic flow:

  1. User tries to log in with their OpenId.
  2. DotNetOpenAuth performs the authentication and if successful, returns a OpenIdClaimedIdentifier and OpenIdFriendlyIdentifier.
  3. I do a Database check to see if there is already a user with this Id.
  4. If there isn't a user already, then create a temporary UserCreateViewModel with both OpenId fields set. This is stored in the TempData.
  5. Redirect to the UserController Create action and display the Create view with this partially complete UserCreateViewModel object.
  6. This bit is the issue The user then completes the other data (DisplayName, etc) and posts the resulting UserCreateViewModel.

The issue is that in between steps 5 and 6, the OpenId parameters get lost if they aren't bound. I don't want to show the user OpenIdClaimedIdentifier or OpenIdFriendlyIdentifier during the create form, but if I remove the data, their binding is lost on the post.

I hope this clarifies the question a bit

+4  A: 

I'm not sure if this is what you're looking for but if you don't want the OpenIdClaimedIdentifier automatically bound then you can add it to the exclude list of the BindAttribute

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude="OpenIdClaimedIdentifier")]UserCreateViewModel viewModel, string ReturnUrl) {
}

Updated after edit

Is there a better way to do this?

Better is a relative term. There are certainly alternative ways of achieving what you want but hidden <input> fields are often used in situations like this and, as you have stated, work.

<%=Html.Hidden("OpenIdClaimedIdentifier") %

Is there any particular reason you don't want to use a hidden field? This would help us answer your question better.

Is it because you are concerned about security? From the way you have described your logic flow, using a hidden <input> would leave you vulnerable to someone changing the authenticated OpenIdClaimedIdentifier and OpenIdFriendlyIdentifier hidden values before submitting. If this is your concern then you could encrypt the data parsed back to the client.

Alternative solutions are:

  • Store the data in the server Session.

    Session["OpenIdClaimedIdentifier"] = value;

  • Or split your process into two stages (consisting of 2 database commits). Update At step 4 when you have confirmed the OpenId authentication you create a user record in your database, get the unique record id created and store it in the authentication cookie (as the user is authenticated at this point). You then redirect to an 'edit user details'. The 'edit' page then takes the user id from the authentication cookie to look up the user record and not from the form.

If you are performing the necessary security checks before the data is saved then I don't see anything wrong with using hidden fields.

David G
Thank you for the updated answer. The 2 stage DB commit process wasn't something I considered. I'll keep investigating and see what I can find.
Alastair Pitts
Dang edit timer: I'm happy to use the hidden field if that's a standard way of doing it. I like the idea of the 2 stage process, but won't that mean at one point i'll have to store the model's user ID on the form for the DB update? Or is this where the [Bind] Exclude works?
Alastair Pitts
@Alistair You would usually store the user ID in the authentication cookie. That way you know the user ID hasn't been changed. See my update.
David G
This is how I've chosen to approach the matter. I did a bit of a refactor of my code flow and decided this was the best way to do it.I appreciate the effort and edits made. Thanks.
Alastair Pitts
+1  A: 

The issue is that you have a two stage process here with half the data coming from the DotNetOpenAuth call and the other half coming from the user. As you want the DotNetOpenAuth call to occur first you need to find some way to store that data until the final save to database call.

This should be done on the client, so the usual approach to achieve this sort of thing by having hidden fields...

<%=Html.Hidden("OpenIdClaimedIdentifier") %>
<%=Html.Hidden("OpenIdFriendlyIdentifier") %>

The other client side options are cookies or URL and neither seem more appropriate than hidden fields here.

Alternatively to could restructure your approach so that your open auth calls creates the user account and saves it to the database with details retrieved from the DotNetOpenAuth call. Then redirect the user to an Edit Account page where they could then update their details.

Russell Giddings
+1  A: 

If you want to use the HiddenInput Attribute, And at the same time use validation attribute on your class you could modify your UserCreateViewModel this way :

public class UserCreateViewModel
{
    [HiddenInput(DisplayValue=false)]
    public string OpenIdClaimedIdentifier { get; set; }
    [HiddenInput(DisplayValue=false)]
    public string OpenIdFriendlyIdentifier { get; set; }
    [Required]
    public string Displayname { get; set; }
    [Required]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }
    [Required]
    [DataType(DataType.PhoneNumber)]
    public string PhoneNumber { get; set; }
}

To display your model with the attribute in your view you use

<%= Html.EditorForModel() %>

or for a single field:

<%= Html.EditorFor(m => m.OpenIdClaimedIdentifier)%>

That way your model would validate automatically and you'll have hidden field for your OpenIdClaimedIdentifier and OpenIdFriendlyIdentifier. To make sure their value hasn't change i would use a cookie...

moi_meme