自定义路由不适用于默认 mvc 路由

Custom route doesn't work with default mvc route

我正在按照 Microsoft Contoso University Tutorial 学习 MVC。

到目前为止我已经创建了 3 个模型

  1. 课程
  2. 注册人数
  3. 学生

我有 2 个控制器

  1. 家庭控制器
  2. 学生控制器

我可以查看学生列表、学生的详细信息(包括课程内容和成绩)、编辑学生详细信息以及添加新学生。

在这一点上,我想了解更多关于路由的知识,因为我一直在使用默认的 Map Route

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

我有一个学生数据库,所有学生的姓氏都是唯一的。我知道通常不建议这样做,因为姓氏不是唯一的,但它适用于这个小型学习项目。所以我想要实现的是能够输入 /students/{action}/{lastname} 并查看有关学生的详细信息。所以我在我的学生控制器中创建了一个新的操作方法。

public ActionResult Grab(string studentName)
{
    if (studentName == null)
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);

    var student = db.Students.FirstOrDefault(x => x.LastName.ToLower() == studentName.ToLower());
    if (student == null)
        return HttpNotFound();

    return View(student);
}

并且我在 RouteConfig.cs 文件中添加了一条新路线

//Custom route
routes.MapRoute(
    name: null,
    url: "{controller}/{action}/{studentName}",
    defaults: new { controller = "Student", action = "Grab", id = UrlParameter.Optional }
);

最后,我创建了一个名为 Grab 的新视图,用于显示学生的详细信息。

现在,当我输入 /student/grab/Norman 时,它会向我提供姓氏为 Norman

的学生的详细信息

太棒了。但现在我有一个问题。当我尝试使用我的一些原始网址时,例如 /Student/Details/1 他们不再工作了。我收到 400 错误。

我做的第一件事是将我的默认路线移到我制作的自定义路线上方

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
//Custom route
routes.MapRoute(
    name: null,
    url: "{controller}/{action}/{studentName}",
    defaults: new { controller = "Student", action = "Grab", id = UrlParameter.Optional }
);

这解决了这个问题,但在我之前工作的抓取路线上导致了 400 错误。我怎样才能同时使用这两个而不会出现 400 错误?

这是我的完整 RouteConfig.cs 文件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace ContosoUniversity
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
            //Custom route
            routes.MapRoute(
                name: null,
                url: "{controller}/{action}/{studentName}",
                defaults: new { controller = "Student", action = "Grab", id = UrlParameter.Optional }
            );


        }
    }
}

这是我的整个 StudentController.cs 文件

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using ContosoUniversity.DAL;
using ContosoUniversity.Models;

namespace ContosoUniversity.Controllers
{
    public class StudentController : Controller
    {
        private SchoolContext db = new SchoolContext();

        // GET: Student
        public ActionResult Index()
        {
            return View(db.Students.ToList());
        }

        // GET: Student/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Student student = db.Students.Find(id);
            if (student == null)
            {
                return HttpNotFound();
            }
            return View(student);
        }

        public ActionResult Grab(string studentName)
        {
            if (studentName == null)
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);

            var student = db.Students.FirstOrDefault(x => x.LastName.ToLower() == studentName.ToLower());
            if (student == null)
                return HttpNotFound();

            return View(student);
        }

        // GET: Student/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Student/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "LastName, FirstMidName, EnrollmentDate")]Student student)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    db.Students.Add(student);
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
            }
            catch (DataException /* dex */)
            {
                //Log the error (uncomment dex variable name and add a line here to write a log.
                ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
            }
            return View(student);
        }

        // GET: Student/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Student student = db.Students.Find(id);
            if (student == null)
            {
                return HttpNotFound();
            }
            return View(student);
        }

        // POST: Student/Edit/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost, ActionName("Edit")]
        [ValidateAntiForgeryToken]
        public ActionResult EditPost(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            var studentToUpdate = db.Students.Find(id);
            if (TryUpdateModel(studentToUpdate, "",
               new string[] { "LastName", "FirstMidName", "EnrollmentDate" }))
            {
                try
                {
                    db.SaveChanges();

                    return RedirectToAction("Index");
                }
                catch (DataException /* dex */)
                {
                    //Log the error (uncomment dex variable name and add a line here to write a log.
                    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
                }
            }
            return View(studentToUpdate);
        }

        // GET: Student/Delete/5
        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Student student = db.Students.Find(id);
            if (student == null)
            {
                return HttpNotFound();
            }
            return View(student);
        }

        // POST: Student/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            Student student = db.Students.Find(id);
            db.Students.Remove(student);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

我会提供任何其他需要的详细信息。我再次希望能够输入 http://localhost:49706/Student/Details/1http://localhost:49706/Student/Grab/Alexander 并获得完全相同的详细信息,因为 Alexander 的学生 ID 为 1。

您想将自定义路由与默认路由区分开来url

public static void RegisterRoutes(RouteCollection routes) {
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    //Custom route
    routes.MapRoute(
        name: "Students",
        url: "Student/{action}/{id}",
        defaults: new { controller = "Student", action = "Grab", id = UrlParameter.Optional }
    );

    //General default route
    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

操作参数也应与路由模板参数相匹配

// GET: Student/Details/5
public ActionResult Details(int? id) {
    if (id == null) {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var student = db.Students.Find(id);
    if (student == null) {
        return HttpNotFound();
    }
    return View(student);
}

public ActionResult Grab(string id) {
    if (id == null)
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);

    var student = db.Students.FirstOrDefault(x => x.LastName.ToLower() == id.ToLower());
    if (student == null)
        return HttpNotFound();

    return View(student);
}

现在可以正确匹配以下内容

Student/Details/1 
Student/Grab/Alexander

提供姓氏为 Alexander 的学生的 studentId 为 1

两条路线相同(从模式匹配的角度来看):

{controller}/{action}/{id}

{controller}/{action}/{studentName}

idstudentName 只是占位符。它们除此之外没有任何意义,所以你基本上定义了一个路由两次:

{controller}/{action}/{someindicator}

最终发生的事情是,首先命中的任何路由都会为请求提供服务。

因此,如果你想使用相同的 "pattern",你需要以某种方式区分路由。我建议直接在路线中指明动作:

routes.MapRoute(
                name: "StudentDetails",
                url: "{controller}/Details/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
            //Custom route
            routes.MapRoute(
                name: "GrabStudent",
                url: "{controller}/Grab/{studentName}",
                defaults: new { controller = "Student", action = "Grab", id = UrlParameter.Optional }
            );