5

背景

我们网站上有一些链接,其格式如下:

http://oursite.com/books/c_sharp_in_depth_12345

为了处理这个问题,我们使用了一个简单的属性,叫做Url

public class Book
{
    public string Url { get; set;}
}

基本上,URL 来自我们网站的域、网站的一部分(即书籍)、页面名称和用于识别资源的唯一 ID。完整的 URL 存储在数据库中。

我们不喜欢我们的数据库存储部分名称的事实。这是 Web 图层属性的属性,而不是书籍的属性。数据库不应依赖于 web 层。

因此,我们从 URL 中删除该部分并获得以下内容:

public class Book
{
    public string UrlWithoutSection { get; set;}
}

好的,这适用于这个 URL。但是后来我们公司的 SEO 沙皇说我们的 URL 是错误的,而我们的真爱谷歌只有在我们像这样重写我们的 url 时才会爱我们:

http://oursite.com/programming-books/c-sharp-in-depth-12345

呃,哦,我以为我们已经删除了对web层的数据库依赖,但是我们没有。事实证明,我们已经删除了对该部分的依赖,但 URL 的格式仍然存在于数据库中。我们通过将 URL 抽象为一个对象来解决这个问题:

public class OurUrl 
{
    public string title { get; set; }
    public string id { get; set; }
}

酷,现在对 web 层的依赖已经消失了。哦哦,这次我们的CEO来找我们了。我们刚买了一家新公司,现在我们卖杂志。嗯,很棒吗?杂志 URL 将如下所示:

http://oursite.com/magazines/computers/stack-overflow-the-magazine/2012/01/01/12345

好的,没问题,只需创建另一个对象。

public class OurMagazineUrl : OurUrl
{
    public DateTime PublishedDate { get; set; }

    // Magazine enum will have to be created.
    public MagazineType Type { get; set; }  
} 

它有效,但我开始意识到我们有一个大网站的计划。很多网址。许多不同格式的 URL。每次创建一个新类似乎都是一件令人头疼的事情。

简而言之问题

您如何处理 URL,以便 Web 层与业务层和数据层正确解耦?我想出了几个关于解决方案的想法:

有关问题的更多信息

我希望这有助于澄清一些困惑。

我们正在使用 ASP.Net MVC。我们使用路线。我们使用助手。我们将扁平化的 DTO 传递给我们的 Web 层,而不是业务对象。这个问题涉及服务层和 DTO 的爆炸式增长。

这主要是一个高流量的新闻网站,而不是一个行业网站。它可以有许多不同的网址,并且网址可以随时更改。它们可能很复杂,并且可以由管理层任意确定。

URL 示例(不是真实的,出于示例目的而编造)。

 1. http://oursite.com/news/wi/politics/supreme-court/recent-ruling-someid
 2. http://oursite.com/news/wi/politics/election-2012/candidate-x-takes-stand-on-issue-y-someid
 3. http://oursite/com/news/politics/mayor-says-things-are-a-ok-someid
 4. http://oursite.com/news/milwaukee/local-bar-named-to-HOF-someid
 5. http://oursite.com/news/wi/politics/supreme-court-someid
 6. http://oursite.com/news/whatever-cat-our-CEO-wants/subcat1/subcat2/etc/2011/10/31/some-story-someid

以上都是“文章”,我们有一个文章类。一篇文章有​​许多导航属性,例如 AuthorObject、RelatedLinksCollection 等。业务对象太重而无法传递给客户端,因此我们传递了扁平化信息的 DTO(例如 AuthorName)。然而,上述链接可能需要不同的信息,即使它们都是“文章”。

  1. 需要类别、子类别、标题和 ID
  2. 需要类别、子类别、政治类别、标题和 ID
  3. 需要类别、标题和 ID
  4. 需要类别、标题和 ID
  5. 需要类别、子类别、标题和 ID
  6. 需要 CEOCategory、CeoSubcategory、PublishedDate、Title 和 Id

在静态编程语言(例如 c#)中,通常的处理方法是创建单独的 DTO 类。您可以添加继承来减少一些代码,但您仍然会得到多个“文章”dto 类。

public class IArticleDto { 
  public string title { get; set; } 
  public string body { get; set; } 
  public string Category { get; set; }}

public class StateArticleDto: IArticleDto { 
  public string title { get; set; } 
  public string body { get; set; } 
  public string Category { get; set; }}
  public string StateCode { get; set; } 
  public string Subcategory { get; set; } 
}

public class SupremeCourtArticleDto: IArticleDto { 
  public string title { get; set; } 
  public string body { get; set; } 
  public string Category { get; set; }}
  public string Subcategory { get; set; } 
}

public class ArbitraryCeoArticleDto: IArticleDto { 
//who knows
}

等等

以任何可能的方式编写自定义网址的能力不可协商。如果一篇文章与某事(状态、类别等)相关,它可以成为 url 的一部分。

解决方案?

  1. 根据需要继续添加Url对象。多少?至少有十几个,但命名它们会很麻烦。为每个业务对象做一个可以解决名称问题,但这意味着数十或数百个新对象。呸。

  2. IOC - 通过配置将路由模式传递给数据访问层。然后数据访问层可以创建一个完整的 url。url 模式名称仍然是一个问题。

  3. 使用Dictionary<TKey, TValue>,KeyValuePair<TKey, TValue>等来拉入。

  4. 使用ExpandoorDynamicObject作为 url 详细信息。因此 url 将包含几个基本属性(nameid),但必要时可以添加其他属性。

我正在考虑使用 4),因为动态编程似乎比静态语言做得更好。但是,可能只是我最看重它,因为我喜欢玩新玩具(我以前没有使用过expando)。

由于对象爆炸,它比 1) 更好。我不确定 2) 是否适用于复杂的场景。您可以使用 DI 将简单的路由名称 + 路由信息传递给数据层,但在没有额外收益的情况下似乎更难完成。它可能不适用于复杂的路线——为此,我认为您需要在 UI 端有一个规则引擎。

与 3) 相比,我认为 4) 稍微好一些。如果我弄错了,有人纠正我,但动态类型似乎只不过是字典顶部的语法糖,但具有更简洁的代码的优势。只是一个想法。

4

4 回答 4

2

我会直言不讳地说,您提出这个问题并基于您提出的解决方案,您并没有对手头的问题采取鸟瞰的架构视图。我不知道你的应用程序的细节,但是能够有一点前景和描绘未来可能会使用什么的能力将有助于 URI 设计。

您提出的解决方案都是围绕非常具体的程序化细节展开的,而问题是抽象的,根本与编程无关。解决问题就是写下您域中的实体、可能对它们执行的操作以及它们之间的关系。

Bill de hÓra 有一篇关于框架的 Web 资源映射标准的博客文章(这是一个缓存版本,如果真正的版本仍然给出 HTTP 500 错误),Joe Gregorio 写了关于如何RESTify DayTrader的文章,它应该给你正确的想法关于如何设想应用程序的完整 URI 空间。计划、绘图、思考和写作听起来像是您和您的团队需要做的事情。

设计应用程序中 URI 的完整范围和空间后,您就可以实现它了。但是,您是否可以自行决定,然后我建议使用URI 模板自己定义 URI,并使用常规代码来映射将处理 URI 的代码(无论是Controllers、Handlers 还是其他)。在 ASP.NET MVC 中,配置代码是针对RouteTable类编写的,而在 OpenRasta 中,它是针对ResourceSpace类完成的。

于 2011-10-30T21:15:29.740 回答
0

也许你可以维护一个外部 DSL 的状态机。

Main
.*=Product
nj|ny=State
([a-z]*-)*=Title
20[1-9][1-9]->ExpectMonth =Year 
ExpectMonth
[0|1][0-9] = Month -> ExpectDay

等等……

和一个 T4 生成结构代码

struct Url {
 string Product {get;set}
 string State {get;set;}
}

我认为代码生成在一定程度上缓解了“单体类”反模式。

于 2011-10-31T15:17:57.533 回答
0

这是我在#4 的第一次尝试。它可以工作并且动态对象类型可以优雅地工作。

public class ArticleDto 
{
//normal Article properties
//code goes here

//new dyamic property for the Uri Details
public dynamic UriDetails { get; set; }
}

然后我创建了一个自定义类来处理 URI 详细信息。我添加了一个带有名称 + id 的构造函数,因为我总是希望无异常地传递资源名称 + id。

public class UriDetails : DynamicObject
{
    //TODO put error handling in here to ensure that they're not empty?
    public string ResourceName { get; set; }
    public int ResourceId { get; set; }

    public UriDetails(int resourceId, string resourceName)
    {
        ResourceId = resourceId;
        ResourceName = resourceName;
    }
//other code that you need to override
}

然后,在我的 DataAccess 代码中

ArticleDto = new ArticleDto();
//Set the regular properties
//code goes here

//Set the mandatory uri properties
ArticleDto.UriDetails = new UriDetails(id, articleTitle);

//Set any other properties specific to this call
ArticleDto.Date = publishedDate;

注意:这解决了服务层的问题,但是web层仍然需要知道如何构建URL。这将是某种类型的辅助类/规则引擎,它将根据 UriDetails 中的属性名称和类型确定使用什么。

让我知道你的想法。

于 2011-10-31T16:27:40.460 回答
0

您正确地看到需要将您的域实体(书籍、杂志等)与其 URL 的任何感知分离。

您应该需要访问一本书(例如)它的唯一标识符Id- 在您的示例中为12345。任何其他分类元素都应该在表示逻辑中处理,因为您可能想要非 WWW 频道的不同 URI 结构?如果您启动网站多语言?将 /magazines/computers/ 持久保存在数据库中将是一个障碍。

您的 SEO 沙皇的要求可能会随着时间的推移而变化,因为在搜索引擎中排名更高的技术也会发生变化。

因此,这是URL 路由的问题。

在 ASP.NET Webforms 解决方案中,您可以将以下条目添加到 Global.asax 文件中:

void Application_Start(object sender, EventArgs e) 
{
    RegisterRoutes(System.Web.Routing.RouteTable.Routes);
}

public static void RegisterRoutes(System.Web.Routing.RouteCollection routes)
{
    routes.MapPageRoute("Book",
        "{productType}/{categoryName}/{productName}/{productId}",
        "~/Books.aspx");

    routes.MapPageRoute("Magazine",
        "{productType}/{categoryName}/{productName}/{year}/{month}/{day}/{productId}",
        "~/Magazines.aspx");
}

Books.aspx 和 Magazines.aspx 将收集 URL 的相关部分,其中:

var categoryName = Page.RouteData.Values["categoryName"];

当您收集了足够的信息来唯一标识您想要展示的产品时,您可以查询您的域/数据层以获取您需要的信息。

当新产品类型可用时(或您的利益相关者请求新的 URL 结构),您只需添加另一个路由。

于 2011-10-30T21:12:41.167 回答