What are the responsibilities of the components in

2019-05-14 13:11发布

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:

  1. 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 the openid_identifier)?
  2. 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 and LogInModel)?
  3. 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?
  4. Would it be better to add the database as a model instead of making the above models?

1条回答
放我归山
2楼-- · 2019-05-14 13:36

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:

I'm sorry... for 1, Data Annotations suggest the validation is performed on the model, but OpenId.Identifier also provides some functions that can validate input (e.g. TryParse), so would it be more consistent to do all of the validation on the model or is the "place" of validation usually not that strict?

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.

For 3, I agree that the HTTP protocol is stateless, but if I understand correctly cookies are one way to maintain a state and the model provides an alternate way.

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 application programming a state (i.e. an object) is usually persistent throughout the lifetime of the application, so if I were to create a Model in a console app it would exist for as long as there is a reference to it. So when you say that the "objects are newed," does that mean that the Model object is already instantiated (like in a console app) and I just "update" it or does that mean that I construct a new Model?

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.

查看更多
登录 后发表回答