如何使用breeze为SPA设置客户端元数据

时间:2014-12-22 23:41:33

标签: angularjs breeze

大家好我正在使用angular and breeze处理一个小型报表库SPA,允许用户管理他们创建的报表。我知道你可以使用breeze和EF上下文通过引用API来构建模型。但是如何实现它我想让EF离开它。 API(WebAPI 2)基本上调用其他存储库来完成工作,就像接口一样。返回的结果只是一个json对象。我查看了Breeze网站上的Edmunds示例,我可以看到如何构建客户端模型以及处理返回的映射。我当前的问题是我不确定我在jsonResultsAdapter中的映射是否正确。或者我可能会遗漏一些关于映射应该如何工作的东西。

我正在使用一些我已经存入API的模拟数据进行测试,直到我可以使用它。一旦绑定和映射,我就可以违背实际数据。这是我到目前为止: 模拟数据是一个报表对象,其中包含一个名为标签的内部集合(基本上是标签),一个报表可以有多个标签,从标签上可以有多个报表。

//report dto
public class ReportDto
{

    public Int64 ReportId { get; set; }
    public string ReportName { get; set; }
    public string ReportDescription { get; set; }
    public string ReportDateCreated { get; set; }
    public string ReportOwner { get; set; }
    public IEnumerable<ReportLabelDto> ReportLabels { get; set; } 
}

public class ReportLabelDto
{
    public Int64 LabelId { get; set; }
    public string LabelName { get; set; }
    public bool IsPrivate { get; set; }
    public bool IsFavorite { get; set; }
    public IEnumerable<ReportDto> Reports { get; set; }//placeholder?
}

这是目前在webapi控制器中使用的代码,此时仅用于测试:

[Route ("reportlibrary/myreports/{userid}")]
public IEnumerable<ReportDto> GetAllReports(string userId)
{
    List<ReportDto> result = new List<ReportDto>();
    List<ReportLabelDto> label = new List<ReportLabelDto>();
    //create 5 reports
    for (int i = 0; i < 5; i++)
    {
        ReportDto r = new ReportDto();
        ReportLabelDto l = new ReportLabelDto();

        r.ReportId = i;
        r.ReportOwner = "John Smith";
        r.ReportDateCreated = DateTime.Now.ToString();
        r.ReportDescription = "Report Description # " + i.ToString();
        r.ReportName = "Report Description # " + i.ToString();        
        //generate labels
        l.LabelId = i;
        l.LabelName = "Special Label" + i.ToString();
        l.IsPrivate = true;
        l.IsFavorite = false;
        label.Add(l);
        r.ReportLabels = label;
        result.Add(r);
    }

    return result;
}

目前回来的对象如下所示:

[{"ReportId":0,"ReportName":"Report Description # 0","ReportDescription":"Report Description # 0","ReportDateCreated":"12/22/2014 6:32:05 PM","ReportOwner":"John Smith","ReportLabels":[{"LabelId":0,"LabelName":"Special Label0","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":1,"LabelName":"Special Label1","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":2,"LabelName":"Special Label2","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":3,"LabelName":"Special Label3","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":4,"LabelName":"Special Label4","IsPrivate":true,"IsFavorite":false,"Reports":null}]},{"ReportId":1,"ReportName":"Report Description # 1","ReportDescription":"Report Description # 1","ReportDateCreated":"12/22/2014 6:32:05 PM","ReportOwner":"John Smith","ReportLabels":[{"LabelId":0,"LabelName":"Special Label0","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":1,"LabelName":"Special Label1","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":2,"LabelName":"Special Label2","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":3,"LabelName":"Special Label3","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":4,"LabelName":"Special Label4","IsPrivate":true,"IsFavorite":false,"Reports":null}]},...]

我有服务和控制器都在说话,我可以点击api并返回一个对象,所以我现在要省略该代码。

对于js模型,我按如下方式定义了报告对象:

app.factory('model', function () {

    var DT = breeze.DataType;
    return {
        initialize: initialize
    }

    function initialize(metadataStore) {
        metadataStore.addEntityType({
            shortName: "Report",
            namespace: "Inform",
            dataProperties: {
                reportid: { dataType: DT.Int64, isPartOfKey: true },
                reportname: { dataType: DT.String },
                reportdescription: { dataType: DT.String },
                reportdatecreated: { dataType: DT.String },
                reportowner: { dataType: DT.String },
                mappedlabels: { dataType: DT.Undefined },
                ishared: { dataType: DT.Bool },
                isfavorite: { dataType: DT.Bool }
            },
            navigationProperties: {
                labels: {
                    entityTypeName: "Label:#Inform", isScalar: false,
                    associationName: "Report_Labels"
                }
            }
        });
        metadataStore.addEntityType({
            shortName: "Label",
            namespace: "Inform",
            dataProperties: {
                labelid: { dataType: DT.Int64, isPartOfKey: true },
                reportid: { dataType: DT.Int64 },
                labelname: { dataType: DT.String },
                ispublic: { dataType: DT.Bool },
                mappedreports: { dataType: DT.Undefined }
            },
            navigationProperties: {
                labels: {
                    entityTypeName: "Report:#Inform", isScalar: false,
                    associationName: "Report_Labels", foreignKeyNames: ["reportid"]
                }
            }
        });
    }
})

这是我认为问题所在,我不明白这个适配器足以确保我收到我认为我以及正确处理映射的内容:

/* jsonResultsAdapter: parses report data into entities */
app.value('jsonResultsAdapter',
    new breeze.JsonResultsAdapter({

        name: "inform",

        extractResults: function (data) {
            var results = data.results;
            if (!results) throw new Error("Unable to resolve 'results' property");
            // Parse only the make and model types
            return results && (results.reportHolder || results.labelHolder);
        },

        visitNode: function (node, parseContext, nodeContext) {
            //Report parser
            if (node.reportid && node.labels) {

                node.mappedlabels = node.labels;
                node.labels = [];
                return { entityType: "Report" }
            }

            // Label parser
            else if (node.labelid && node.reports) {

                node.mappedreports = node.reports;
                node.mappedreports = [];
                return { entityType: "Label" };
            }
        }

    }));

当我单步执行chrome中的代码时,我可以看到返回了一个对象。有5个报告,每个报告有5个标签(我知道标签目前显示空报告)。当我在jsonResultsAdapter中设置断点时,我可以看到带有5个对象的结果,但是传递回服务的结果是null。任何人都可以帮我验证模型和映射是否正确,或者您是否在jsonResultsAdapter中看到任何不合适的地方。我也很欣赏有关我可能想做的事情的任何建议。我现在感觉非常黑盒子,因为我没有看到/理解解决这个映射片的好方法。

-cheers

3 个答案:

答案 0 :(得分:1)

看起来您定义的所有属性都没有正确的camelCased或PascalCased。 Breeze.js将寻找匹配的属性 - 但除非我遗漏了你已经定义的东西,否则它不会降低它们。

您需要像这样设置模型属性 -

ReportName: { dataType: DT.String },

然后在结果适配器中,您需要像这样正确地检查属性名称 -

if (node.ReportId && node.Labels) {

答案 1 :(得分:1)

在这里,我将了解一些PW Kad的观察结果,并添加一些我自己的观察结果。

让我们首先了解元数据和JsonResultsAdapter的不同角色。

元数据是您为客户端实体模型定义架构和验证规则的地方。它描述了Breeze保存在缓存中并使程序可用的实体对象。

但元数据与从服务器到达的JSON有效负载无关。这是一个完全独立和较低级别的问题。这是JsonResultsAdapter的关注点。

JsonResultsAdapter 位于由于AJAX调用而来自服务器的JSON数据与缓存中的实体之间的管道中。 JSON数据不必像实体那样整形。他们不必遵守您编写的元数据。元数据描述您想要使用它们的实体。 JSON是服务为您提供的悲惨现实。 JsonResultsAdapter可以缩小差距。

实体架构是否符合JSON有效载荷的形状是任何人的猜测。通常,JSON数据需要稍微调整一下。操作JSON&#34;节点&#34; JsonResultsAdapter的工作是MetadataStore的工作。进入Breeze可以映射到您的实体的东西。如果JSON有效负载非常接近元数据描述的实体形状,则工作会更容易。让我们希望您的JSON与您的实体保持一致。

元数据和具体化

现在,在将JSON映射到实体时,Breeze确实使用了元数据。 NamingConventionJsonResultsAdapter,规定了如何在客户端实体属性名称和服务属性名称之间进行转换。 &#34;实现&#34;进程期望从NamingConvention.none出现的JSON具有预期的服务属性名称。这就是为什么我坚持认为节点属性名称(如果你需要它们)在PascalCase中拼写...假设你使用的是标准的Breeze camelCase约定,并且你的服务实际上确实拼写了属性名称PascalCase。

  

大多数C#和Java服务器都可以。 Rails和节点服务器通常不会;他们也在服务器上使用camelCase ...这意味着如果您正在使用来自这些服务器的Feed,那么您需要JsonResultsAdapter

理想情况下NamingConvention必须做的很少。 JSON属性名称通常很容易地映射到实体属性名称,您可以使用node.ReportId = node.ReportId; node.ReportName = node.ReportName; node.ReportDescription = node.ReportDescription; 处理所需的任何翻译。你似乎就是这种情况(见下文)。

肯定你没有完成你向我们展示的代码:

JsonResultsAdapter

这是最精细的#34;没有操作&#34;代码我很长一段时间都见过。我不知道你有什么想法。

在识别对应于JSON节点的EntityType 时,通常需要 $type 。如果您不使用Json.Net序列化程序从.NET获取数据,则您的服务器可能不会使用JSON数据向下发送类型名称。您的JSON节点缺少Breeze默认查找的JsonResultsAdapter属性。

如果这是你的情况(似乎是这样),你的JsonResultsAdapter.visitNode必须提供类型名称。

显然,您可以通过检查每个节点的关键属性来为您的数据执行此操作。似乎关键属性名称本身包含类型名称的区别部分。

也许您的visitNode: function (node, parseContext, nodeContext) { //Report parser if (node.ReportId) { return { entityType: "Report:#Inform" } } // Label entity else if (node.ReportLabels) { return { entityType: "Label:#Inform" }; } } 方法可能如下所示:

:#Inform

请注意,entityType名称属性中包含名称空间NamingConvention.camelCase)。命名空间是每个EntityType的全名的一部分......你必须提供它。

请注意,我没有做任何属性名称映射。我没有看到任何理由。节点属性名称看起来就像实体元数据名称...除了PascalCasing ...我们用reportid: { dataType: DT.Int64, isPartOfKey: true }, reportname: { dataType: DT.String }, reportdescription: ... 来处理它。

错误的元数据?

实际上,节点属性名称看起来像元数据中的实体属性名称,即使在考虑Pascal-to-camel-case转换之后也是如此。我认为这就是PW Kad所指出的。

问题是元数据中的实体属性名称是全部小写。不是camelCase;小写。例如:

reportId: { dataType: DT.Int64, isPartOfKey: true },
reportName: { dataType: DT.String },
reportDescription: ...

不应该是:

ReportId
ReportName 
ReportDescription

这与您的JSON属性名称

很好地对应
NamingConvention

为什么要在客户端上使用所有小写属性名称。

  

您可以全部小写并编写一个非常古怪的自定义[BreezeController]来在客户端实体名称和服务名称之间导航。在我的书中,这很多工作都没有用。

为什么JSON中没有$ type?

我只是滚动到这个问题的顶部并意识到您的服务器是用C#编写的,看起来您正在使用Web API。

为什么不使用JsonResultsAdapter属性注释Web API控制器?这样做会将控制器配置为以Breeze客户端默认理解的方式序列化数据。您可能根本不需要JsonResultsAdapter

不要更改类型名称!

再看一遍,我看到另一个问题迫在眉睫。您的服务器端类名具有后缀&#34; Dto&#34;但是您不希望在客户端类型名称上添加后缀。您还在一种情况下完全更改类型名称:&#34; ReportLabelDto&#34;到&#34;标签&#34;。

Breeze有一个变形属性名称的命名约定。它没有&#34;实体类型&#34;的命名约定。名。

如果您坚持在客户端和服务器上使用不同的类型名称,那将是一种巨大的痛苦。我不确定是否可以做到。

是的,您可以在{{1}}中变形实体名称。这涵盖了 中的通信。但是您还必须担心出局 上的通信。当你要求它保存一个类的实体时,服务器就不会高兴了#34;标签&#34; ......它一无所知。

在我写作时,我无法想到一个简单的方法。目前,Breeze要求服务器端类型名称与客户端EntityType名称相同。如果服务器上的类型名称为&#34; ReportLabelDto&#34;,则您必须将相应的EntityType命名为&#34; ReportLabelDto&#34;。没有简单的方法。

幸运的是,与在任何地方出现的属性名称不同,您通常不会在客户端上引用EntityType名称,因此将其称为&#34; ReportLabelDto&#34;不应该是一件大事。

答案 2 :(得分:0)

感谢PW Kad指出我正确的方向。我忘记了区分大小写的部分。此外,我回过头来重新评估我在jsonResultsAdapter.js文件中尝试做的事情。我终于意识到这个文件的工作方式类似于automapper,我觉得breeze会在内部解析映射。 (也许它确实与EF上下文有关)但是在创建客户端元时我必须明确设置映射。更新的代码现在显示:

app.value('jsonResultsAdapter',
    new breeze.JsonResultsAdapter({

        name: "inform",

        extractResults: function (data) {
            var results = data.results;
            if (!results) throw new Error("Unable to resolve 'results' property");
            // Parse only the make and model types
            return results;
        },

        visitNode: function (node, parseContext, nodeContext) {
            //Report parser
            if (node) {

                node.ReportId = node.ReportId;
                node.ReportName = node.ReportName;
                node.ReportDescription = node.ReportDescription;
                node.ReportDateCreated = node.ReportDateCreated;
                node.ReportOwner = node.ReportOwner;
                node.ReportLabels = node.ReportLabels;
                node.ReportLabels = [];
                node.IsShared = node.IsShared;
                node.IsFavorite = node.IsFavorite;

                return { entityType: "Report" }
            }

            // Label parser
            else if (node.ReportLabels) {

                node.LabelId = node.LabelId;
                node.LabelName = nodel.LabelName;
                node.IsPrivate = node.IsPrivate;
                node.IsFavorite = node.IsFavorite;
                node.Reports = node.Reports;
                node.Reports = [];
                return { entityType: "Label" };
            }
        }

    }));

我知道我仍然需要对映射进行一些调整,或者如何从API中解析它,但是要使这个更改正确映射模拟数据并允许它在UI中绑定/显示。

希望这有帮助