I'm trying to understand the MVC pattern and I get the general idea that the Model is responsible for maintaining the state, the View is responsible for displaying the Model and the Controller is responsible for modifying the Model and calling the appropriate View(s). I wanted to try and implement a simple ASP.NET MVC login page that uses OpenID in order to get some understanding of how it all works.
I've downloaded DotNetOpenAuth-3.4.6 and I was looking through the samples, specifically their MVC sample. Unfortunately, the sample doesn't actually have a model, only a controller:
namespace OpenIdRelyingPartyMvc.Controllers
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.RelyingParty;
public class UserController : Controller
{
private static OpenIdRelyingParty openid = new OpenIdRelyingParty();
public ActionResult Index()
{
if (!User.Identity.IsAuthenticated)
{
Response.Redirect("~/User/Login?ReturnUrl=Index");
}
return View("Index");
}
public ActionResult Logout()
{
FormsAuthentication.SignOut();
return Redirect("~/Home");
}
public ActionResult Login()
{
// Stage 1: display login form to user
return View("Login");
}
[ValidateInput(false)]
public ActionResult Authenticate(string returnUrl)
{
var response = openid.GetResponse();
if (response == null)
{
// Stage 2: user submitting Identifier
Identifier id;
if (Identifier.TryParse(Request.Form["openid_identifier"], out id))
{
try
{
return openid.CreateRequest(Request.Form["openid_identifier"]).RedirectingResponse.AsActionResult();
}
catch (ProtocolException ex)
{
ViewData["Message"] = ex.Message;
return View("Login");
}
}
else
{
ViewData["Message"] = "Invalid identifier";
return View("Login");
}
}
else
{
// Stage 3: OpenID Provider sending assertion response
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
Session["FriendlyIdentifier"] = response.FriendlyIdentifierForDisplay;
FormsAuthentication.SetAuthCookie(response.ClaimedIdentifier, false);
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
case AuthenticationStatus.Canceled:
ViewData["Message"] = "Canceled at provider";
return View("Login");
case AuthenticationStatus.Failed:
ViewData["Message"] = response.Exception.Message;
return View("Login");
}
}
return new EmptyResult();
}
}
}
Perhaps the sample is too simple to actually involve a model, since all of the state information is contained within the Cookie. Subsequently, I've implemented a simple database which has a single Users table:
Users
+ user_id
+ open_id
+ last_login
I presume that I would need a LoginModel
:
public class LogInModel
{
[Required]
[DisplayName("OpenID")]
public string OpenID { get; set; }
}
A DisplayModel
:
public class DisplayModel
{
[DisplayName("User ID")]
public string UserID{ get; set; }
[DisplayName("OpenID")]
public string OpenID { get; set; }
[DisplayName("Last Login")]
public DateTime LastLogin{ get; set; }
}
Additionally, I may need a ModifyModel
, but the DisplayModel
can be reused and possibly renamed to UserModel
(to properly reflect the use of the model).
So now I have several questions:
- Is the controller responsible for verifying user input or is that done when the input is passed to the model (i.e. calling
Identifier.TryParse
on theopenid_identifier
)? - I want to allow the user login, change their information (i.e. the user_id) and view their information (i.e. user_id, open_id and last_login). How many models do I need (
ModifyModel
,DisplayModel
andLogInModel
)? - What is the lifetime of a model? Does the model exist somewhere statically or is it just created by the controller and passed to the view?
- Would it be better to add the database as a model instead of making the above models?
1) It could be yes, but a better approach would be to use Data Annotations on the ViewModel.
2) One model will do. A model should represent an overall object, in this case a "User". If the information required for each View differs greatly, then seperate them out into View Models.
3) Not sure what you mean. MVC (and ASP.NET in general) is based on the HTTP protocol, and is thus stateless. So when you hit a URL, a Controller is assigned, then objects are newed up as the code requires it - this includes a database connection. Then when the request is finished, everything is gone (managed resources anyway). Try not to get confused with the word "model". It's not a physical entity, rather an area of your programming model.
4) Generally, your "model" is your Repository/DAL/ORM, which wraps your underlying database, and represents your domain model. Your View's shouldn't be concerned with the domain. That is the job of your Controller/Model. Your View should work with what it needs, and no more. This is why as a rule of thumb, never bind directly to an ORM model, use a ViewModel.
Edit - in response to questions in comments:
Put the Data Annotations on the ViewModel which are representations of the Model created to make life easier for the View. Data Annotations should not be put on your actual model entities (Entity Framework, L2SQL, etc). Data Annotations should be used for input validation (password comparing, length of characters, phone numbers, email addresses, etc). Business Validation should be done in the domain. I would say that OpenId is a service and not part of the domain. If they have some validation functions, you could wrap those calls in custom data annotations and place them on your ViewModel. That would be a clean and consistent approach.
Your right, cookies are one way to maintain state. A common use for this is the Forms Authentication ticket. Another one is session. TempData uses Session (in a smart way - automatic ejection). The model is not an "alternate way" - it does not maintain state. Will talk more about that below.
In a console app - your right. As long as the console application is running, the objects are alive. But in a ASP.NET MVC Web Application, the "parent" is a ASP.NET Worker Thread, assigned when a request comes in. All of the required objects (controller, database connection, repository, domain objects) are "children" of this thread, if that makes sense. Once that thread is gone, so is all the related objects.
There is no "magic instantiation" of the model, again - the Model is an overall view/representation of your domain, which usually consists of the domain model (entities, business logic) and the repository.
The one exception is "model-binding". When you submit a Form to an
[HttpPost]
action that is strongly-typed to a "model" (should be a ViewModel). ASP.NET MVC (via reflection) will construct this "model" based on the fields in the HTTP POST.When you do things like "UpdateModel", all that is doing is updating an object you supply with what came into the action method via the model binding. No actual database is being updated.
Don't know what else i can say. You seem to be having confusion about the "model". May i suggest you grab a copy of Steven Sanderson's Pro ASP.NET MVC 2 Framework book. It's fantastic, explains everything from the ground up - in simple terms, then ramps up the pace so your an expert by the end of the book.