导航属性上的Web Api OData Post
本文关键字:Api OData Post Web 属性 导航 | 更新日期: 2023-09-27 18:00:38
我正试图在ICollection中持久化一个实体及其相关对象。错误消息为(服务器名称已删除):
System.Data.Services.Client.DataServiceRequestException:System.Data.Services.Client.DataServiceClientException:未找到与请求匹配的HTTP资源URI'服务.立面.报告/日期/报告(8236)/布局数据'
找不到为模板为"~/entityset/key/navigation"的OData路径选择操作的路由约定。
一些测试客户端代码:
string uriString = "Service.Facade.Reports/odata/";
Uri uri = new Uri(uriString);
DataServiceContext context = new DataServiceContext(uri, DataServiceProtocolVersion.V3);
var layoutData = new ReportLayoutData { Data = new byte[] { 1, 2, 3 } };
var list = new List<ReportLayoutData>();
list.Add(layoutData);
var report = new ReportLayout { LayoutData = list, Name = "thing" };
context.AddObject("Reports", report);
context.AddRelatedObject(report, "LayoutData", list[0]); //Newly Added for Web API
DataServiceResponse response = context.SaveChanges();
如果没有对AddRelatedObject的调用,该服务将用于添加报表。但是当我遍历服务时,导航属性不会自动序列化,所以这就是我添加AddRelatedObject的原因。以前,同样的代码(没有AddRelatedObject)与WCF数据服务一起工作,我现在正尝试转换为Web API。
在服务级别,我似乎需要添加一个路由约定,这就是我所认为的:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<ReportLayout>("Reports");
builder.EntitySet<ReportLayoutData>("ReportLayoutData");
builder.EntitySet<DatabaseInstance>("DataSources");
builder.EntitySet<DataSourceObject>("DataSourceObject");
config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
我将其添加到控制器中,以支持导航属性上的POST/PUT。
// POST PUT odata/Reports(5)/LayoutData
[AcceptVerbs("POST", "PUT")]
public async Task<IHttpActionResult> CreateLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
ReportLayout report = await db.Reports.FindAsync(key);
if (report == null)
{
return NotFound();
}
switch (navigationProperty)
{
case "LayoutData":
string layoutKey = GetKeyFromLinkUri<string>(link);
ReportLayoutData reportLayout = await db.ReportsData.FindAsync(layoutKey);
if (reportLayout == null)
{
return NotFound();
}
report.LayoutData = Enumerable.Range(0,0).Select(x=>reportLayout).ToList();
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
default:
return NotFound();
}
}
// Helper method to extract the key from an OData link URI.
private TKey GetKeyFromLinkUri<TKey>(Uri link)
{
TKey key = default(TKey);
// Get the route that was used for this request.
IHttpRoute route = Request.GetRouteData().Route;
// Create an equivalent self-hosted route.
IHttpRoute newRoute = new HttpRoute(route.RouteTemplate,
new HttpRouteValueDictionary(route.Defaults),
new HttpRouteValueDictionary(route.Constraints),
new HttpRouteValueDictionary(route.DataTokens), route.Handler);
// Create a fake GET request for the link URI.
var tmpRequest = new HttpRequestMessage(HttpMethod.Get, link);
// Send this request through the routing process.
var routeData = newRoute.GetRouteData(
Request.GetConfiguration().VirtualPathRoot, tmpRequest);
// If the GET request matches the route, use the path segments to find the key.
if (routeData != null)
{
ODataPath path = tmpRequest.GetODataPath();
var segment = path.Segments.OfType<KeyValuePathSegment>().FirstOrDefault();
if (segment != null)
{
// Convert the segment into the key type.
key = (TKey)ODataUriUtils.ConvertFromUriLiteral(
segment.Value, ODataVersion.V3);
}
}
return key;
}
POCO使用
[Serializable]
public partial class ReportLayout
{
public const string DefaultName = "Report...";
public const string DefaultGroup = "Uncategorized";
public ReportLayout()
{
LayoutData = new List<ReportLayoutData>();
Name = DefaultName;
Group = DefaultGroup;
UserDefinedQuery = false;
}
public virtual DatabaseInstance DatabaseInstance { get; set; }
public virtual ICollection<ReportLayoutData> LayoutData { get; set; }
public string Name { get; set; }
public string Group { get; set; }
public int ReportLayoutID { get; set; }
public string Query { get; set; }
public bool UserDefinedQuery { get; set; }
public void SetData(byte[] data)
{
if (LayoutData.Count() > 0)
{
LayoutData.ElementAt(0).Data = data;
}
else
{
LayoutData.Add(new ReportLayoutData() {Data = data});
}
}
}
[Serializable]
public class ReportLayoutData
{
public byte[] Data { get; set; }
public virtual ReportLayout ReportLayout { get; set; }
public int ReportLayoutDataID { get; set; }
}
事实证明我几乎是对的,但在服务器端和客户端的代码也需要修改,犯了几个愚蠢的错误。
CreateLink操作中的服务器端修复:
-
report.LayoutData = Enumerable.Range(0,0).Select(x=>reportLayout).ToList(); => report.LayoutData = Enumerable.Range(0,1).Select(x=>reportLayout).ToList();
天哪! -
string layoutKey = GetKeyFromLinkUri<string>(link); => int layoutKey = GetKeyFromLinkUri<int>(link);
客户端代码也发生了变化,促使DataServiceContext做正确的事情:
context.AddObject("Reports", report);
context.AddObject("ReportLayoutData", layoutData);
context.AddLink(report, "LayoutData", layoutData);
上面的DataServicecontext设置转换为对的调用
// POST odata/Reports
public async Task<IHttpActionResult> Post(ReportLayout reportlayout)
// POST PUT odata/Reports(5)/LayoutData
[AcceptVerbs("POST", "PUT")]
public async Task<IHttpActionResult> CreateLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)