プロジェクトにODataを追加する前に、次のように設定されたルート:
config.Routes.MapHttpRoute(
name: "ApiById",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = @"^[0-9]+$" },
handler: sessionHandler
);
config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/{controller}/{action}",
defaults: new { action = "Get" },
constraints: null,
handler: sessionHandler
);
config.Routes.MapHttpRoute(
name: "ApiByIdAction",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = @"^[0-9]+$" },
handler: sessionHandler
すべてのコントローラーは、Get、Put(アクション名はCreate)、Patch(アクション名はUpdate)、およびDeleteを提供します。例として、クライアントはCustomerTypeリクエストにこれらのさまざまな標準URLを使用します。
string getUrl = "api/CustomerType/{0}";
string findUrl = "api/CustomerType/Find?param={0}";
string createUrl = "api/CustomerType/Create";
string updateUrl = "api/CustomerType/Update";
string deleteUrl = "api/CustomerType/{0}/Delete";
次に、他のApiコントローラーと同じアクション名を持つODataコントローラーを追加しました。また、新しいルートを追加しました。
ODataConfig odataConfig = new ODataConfig();
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: odataConfig.GetEdmModel()
);
これまでのところ、クライアント側では何も変更していません。リクエストを送信すると、406 Not Availableエラーが表示されます。
ルートが混同されていますか?どうすれば解決できますか?
ルートが設定される順序が影響します。私の場合、標準のMVCコントローラーとヘルプページもあります。 Global.asax
:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(config =>
{
ODataConfig.Register(config); //this has to be before WebApi
WebApiConfig.Register(config);
});
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
私がプロジェクトを開始したとき、フィルターとrouteTableの部分はありませんでしたneededです。
ODataConfig.cs
:
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes(); //This has to be called before the following OData mapping, so also before WebApi mapping
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Site>("Sites");
//Moar!
config.MapODataServiceRoute("ODataRoute", "api", builder.GetEdmModel());
}
WebApiConfig.cs
:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute( //MapHTTPRoute for controllers inheriting ApiController
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
ボーナスとして、ここに私のRouteConfig.cs
:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute( //MapRoute for controllers inheriting from standard Controller
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
これは、EXACT ORDERである必要があります。コールを移動しようとすると、MVC、Api、またはOdataが404エラーまたは406エラーで壊れてしまいました。
だから私は電話することができます:
localhost:xxx /->ヘルプページ(ホームコントローラー、インデックスページ)に移動します
localhost:xxx/api /-> OData $ metadataにつながる
localhost:xxx/api/Sites-> ODataControllerを継承するSitesControllerのGetメソッドにつながる
localhost:xxx/api/Test-> ApiControllerを継承するTestControllerのGetメソッドにつながります。
OData V4を使用している場合は、using System.Web.Http.OData;
using System.Web.OData;
(最新のライブラリのコメントを確認してください)
oDataControllerで動作します。
RoutePrefixを「api」に設定します。
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<CustomerType>("CustomerType");
config.MapODataServiceRoute(routeName: "ODataRoute", routePrefix: "api", model: builder.GetEdmModel());
どのODataバージョンを使用していますか? OData V4ではSystem.Web.OData
、V3ではSystem.Web.Http.OData
を使用して、正しい名前空間を確認してください。コントローラーで使用される名前空間は、WebApiConfigで使用される名前空間と一致している必要があります。
私の問題は、公開したモデル(builder.EntitySet<ProductModel>("Products");
)ではなく、エンティティモデルを返すことに関連していました。解決策は、エンティティをリソースモデルにマップすることでした。
考慮すべきもう1つのことは、URLで大文字と小文字が区別されることです。
localhost:xxx/api/Sites -> OK
localhost:xxx/api/sites -> HTTP 406
このページの優れたソリューションはどれも役に立たなかった。デバッグすることで、ルートが取得され、ODataクエリが正しく実行されていることがわかりました。ただし、コントローラーが終了した後、それらは破損していました。これは、ODataキャッチオールエラーと思われるものを生成しているのはフォーマットであることが示唆されました:406 Not Acceptable。
Json.NETライブラリに基づくカスタムフォーマッタを追加することでこれを修正しました。
_public class JsonDotNetFormatter : MediaTypeFormatter
{
public JsonDotNetFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
}
public override bool CanReadType(Type type)
{
return true;
}
public override bool CanWriteType(Type type)
{
return true;
}
public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
using (var reader = new StreamReader(readStream))
{
return JsonConvert.DeserializeObject(await reader.ReadToEndAsync(), type);
}
}
public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
if (value == null) return;
using (var writer = new StreamWriter(writeStream))
{
await writer.WriteAsync(JsonConvert.SerializeObject(value, new JsonSerializerSettings {ReferenceLoopHandling = ReferenceLoopHandling.Ignore}));
}
}
_
次に、_WebApiConfig.cs
_にconfig.Formatters.Insert(0, new JsonDotNetFormatter())
行を追加しました。 Jertherの答えに記載されている順序に厳密に固執していることに注意してください。
_public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
ConfigureODataRoutes(config);
ConfigureWebApiRoutes(config);
}
private static void ConfigureWebApiRoutes(HttpConfiguration config)
{
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });
}
private static void ConfigureODataRoutes(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Formatters.Insert(0, new JsonDotNetFormatter());
var builder = new ODataConventionModelBuilder();
builder.EntitySet<...>("<myendpoint>");
...
config.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());
}
}
_
私の場合の問題/解決策はさらに愚かでした。適切なEDMモデルタイプではなく、完全に異なるモデルタイプ、Dictionary
のみを返すアクションにテストコードを残しました。
HTTP 406 Not Acceptable
私のやり方の誤りを伝えることは、同様に愚かです。
私が抱えていた問題は、エンティティセットに「Products」という名前を付け、ProductControllerがあったことです。エンティティセットの名前は、コントローラー名と一致する必要があります。
そう
builder.EntitySet<Product>("Products");
productControllerという名前のコントローラーではエラーが発生します。
/ api/Productは406を提供します
/ api/Productsは404を返します
そのため、代わりにこれを行うことができるいくつかの新しいC#6機能を使用します。
builder.EntitySet<Product>(nameof(ProductsController).Replace("Controller", string.Empty));
GitHubエラーで見つかりました:「odata $ select、$ expand、およびその他をデフォルトで使用できない#511」、それらの解決策は次のようにすることですルートを登録する前の行:
// enable query options for all properties
config.Filter().Expand().Select().OrderBy().MaxTop(null).Count();
私にとって魅力のように働いた。
私のエラーと修正は、上記の回答とは異なりました。
私が抱えていた特定の問題は、WebApi 2.2のODataControllerでmediaReadLink
エンドポイントにアクセスすることでした。
ODataには、仕様に「デフォルトストリーム」プロパティがあり、返されるエンティティに添付ファイルを含めることができます。だから、例えばfilter
などのjsonオブジェクトはオブジェクトを記述し、次にアクセス可能なメディアリンクが埋め込まれています。私の場合、これはPDF記述されているオブジェクトのバージョンです。
ここにはいくつかの巻き毛の問題があります。最初の問題は設定にあります:
_<system.web>
<customErrors mode="Off" />
<compilation debug="true" targetFramework="4.7.1" />
<httpRuntime targetFramework="4.5" />
<!-- etc -->
</system.web>
_
最初はFileStreamResult
を返そうとしていましたが、これはデフォルトのnet45ランタイムではないと思います。そのため、パイプラインは応答としてフォーマットすることができず、406は受け入れられません。
ここでの修正は、HttpResponseMessage
を返し、コンテンツを手動でビルドすることでした。
_ [System.Web.Http.HttpGet]
[System.Web.Http.Route("myobjdownload")]
public HttpResponseMessage DownloadMyObj(string id)
{
try
{
var myObj = GetMyObj(id); // however you do this
if (null != myObj )
{
HttpResponseMessage result = Request.CreateResponse(HttpStatusCode.OK);
byte[] bytes = GetMyObjBytes(id); // however you do this
result.Content = new StreamContent(bytes);
result.Content.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/pdf");
result.Content.Headers.LastModified = DateTimeOffset.Now;
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue(DispositionTypeNames.Attachment)
{
FileName = string.Format("{0}.pdf", id),
Size = bytes.length,
CreationDate = DateTimeOffset.Now,
ModificationDate = DateTimeOffset.Now
};
return result;
}
}
catch (Exception e)
{
// log, throw
}
return null;
}
_
ここでの最後の問題は、有効な結果を返した後、予期しない500エラーが発生することでした。一般的な例外フィルターを追加した後、エラーは_Queries can not be applied to a response content of type 'System.Net.Http.StreamContent'. The response content must be an ObjectContent.
_であることがわかりました。ここでの修正は、コントローラー宣言の先頭から_[EnableQuery]
_属性を削除し、エンティティオブジェクトを返すエンドポイントのアクションレベルでのみ適用することでした。
[System.Web.Http.Route("myobjdownload")]
属性は、Web API 2.2を使用してOData V4にメディアリンクを埋め込み、使用する方法です。完全を期すために、以下の完全なセットアップをダンプします。
まず、私の_Startup.cs
_で:
_[Assembly: OwinStartup(typeof(MyAPI.Startup))]
namespace MyAPI
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// DI etc
// ...
GlobalConfiguration.Configure(ODataConfig.Register); // 1st
GlobalConfiguration.Configure(WebApiConfig.Register); // 2nd
// ... filters, routes, bundles etc
GlobalConfiguration.Configuration.EnsureInitialized();
}
}
}
_
_ODataConfig.cs
_:
_// your ns above
public static class ODataConfig
{
public static void Register(HttpConfiguration config)
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
var entity1 = builder.EntitySet<MyObj>("myobj");
entity1.EntityType.HasKey(x => x.Id);
// etc
var model = builder.GetEdmModel();
// tell odata that this entity object has a stream attached
var entityType1 = model.FindDeclaredType(typeof(MyObj).FullName);
model.SetHasDefaultStream(entityType1 as IEdmEntityType, hasStream: true);
// etc
config.Formatters.InsertRange(
0,
ODataMediaTypeFormatters.Create(
new MySerializerProvider(),
new DefaultODataDeserializerProvider()
)
);
config.Select().Expand().Filter().OrderBy().MaxTop(null).Count();
// note: this calls config.MapHttpAttributeRoutes internally
config.Routes.MapODataServiceRoute("ODataRoute", "data", model);
// in my case, i want a json-only api - ymmv
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeWithQualityHeaderValue("text/html"));
config.Formatters.Remove(config.Formatters.XmlFormatter);
}
}
_
_WebApiConfig.cs
_:
_// your ns above
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// https://stackoverflow.com/questions/41697934/catch-all-exception-in-asp-net-mvc-web-api
//config.Filters.Add(new ExceptionFilter());
// ymmv
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
// so web api controllers still work
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// this is the stream endpoint route for odata
config.Routes.MapHttpRoute("myobjdownload", "data/myobj/{id}/content", new { controller = "MyObj", action = "DownloadMyObj" }, null);
// etc MyObj2
}
}
_
_MySerializerProvider.cs
_:
_public class MySerializerProvider: DefaultODataSerializerProvider
{
private readonly Dictionary<string, ODataEdmTypeSerializer> _EntitySerializers;
public SerializerProvider()
{
_EntitySerializers = new Dictionary<string, ODataEdmTypeSerializer>();
_EntitySerializers[typeof(MyObj).FullName] = new MyObjEntitySerializer(this);
//etc
}
public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
{
if (edmType.IsEntity())
{
string stripped_type = StripEdmTypeString(edmType.ToString());
if (_EntitySerializers.ContainsKey(stripped_type))
{
return _EntitySerializers[stripped_type];
}
}
return base.GetEdmTypeSerializer(edmType);
}
private static string StripEdmTypeString(string t)
{
string result = t;
try
{
result = t.Substring(t.IndexOf('[') + 1).Split(' ')[0];
}
catch (Exception e)
{
//
}
return result;
}
}
_
_MyObjEntitySerializer.cs
_:
_public class MyObjEntitySerializer : DefaultStreamAwareEntityTypeSerializer<MyObj>
{
public MyObjEntitySerializer(ODataSerializerProvider serializerProvider) : base(serializerProvider)
{
}
public override Uri BuildLinkForStreamProperty(MyObj entity, EntityInstanceContext context)
{
var url = new UrlHelper(context.Request);
string id = string.Format("?id={0}", entity.Id);
var routeParams = new { id }; // add other params here
return new Uri(url.Link("myobjdownload", routeParams), UriKind.Absolute);
}
public override string ContentType
{
get { return "application/pdf"; }
}
}
_
_DefaultStreamAwareEntityTypeSerializer.cs
_:
_public abstract class DefaultStreamAwareEntityTypeSerializer<T> : ODataEntityTypeSerializer where T : class
{
protected DefaultStreamAwareEntityTypeSerializer(ODataSerializerProvider serializerProvider)
: base(serializerProvider)
{
}
public override ODataEntry CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext)
{
var entry = base.CreateEntry(selectExpandNode, entityInstanceContext);
var instance = entityInstanceContext.EntityInstance as T;
if (instance != null)
{
entry.MediaResource = new ODataStreamReferenceValue
{
ContentType = ContentType,
ReadLink = BuildLinkForStreamProperty(instance, entityInstanceContext)
};
}
return entry;
}
public virtual string ContentType
{
get { return "application/octet-stream"; }
}
public abstract Uri BuildLinkForStreamProperty(T entity, EntityInstanceContext entityInstanceContext);
}
_
最終結果は、私のjsonオブジェクトにこれらのodataプロパティが埋め込まれることです:
_odata.mediaContentType=application/pdf
odata.mediaReadLink=http://myhost/data/myobj/%3fid%3dmyid/content
_
そして、デコードされたメディアリンク_http://myhost/data/myobj/?id=myid/content
_が_MyObjController : ODataController
_でエンドポイントを起動します。
私にとって問題は、LINQを使用し、ロードされたオブジェクトを直接選択することでした。動作させるにはselect new
を使用する必要がありました。
return Ok(from u in db.Users
where u.UserId == key
select new User
{
UserId = u.UserId,
Name = u.Name
});
これはnot work:
return Ok(from u in db.Users
where u.UserId == key
select u);
私の場合、非パブリックプロパティセッターをパブリックに変更する必要がありました。
public string PersonHairColorText { get; internal set; }
以下に変更する必要があります。
public string PersonHairColorText { get; set; }
私の場合(odata V3)、OdataControllerの名前をODataConventionModelBuilderで提供されているものと同じ名前に変更する必要があり、問題を解決しました。
私のコントローラー:
public class RolesController : ODataController
{
private AngularCRMDBEntities db = new AngularCRMDBEntities();
[Queryable]
public IQueryable<tROLE> GetRoles()
{
return db.tROLEs;
}
}
ODataConfig.cs:
public class ODataConfig
{
public static void Register(HttpConfiguration config)
{
ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<WMRole>("RolesNormal");
modelBuilder.EntitySet<WMCommon.DAL.EF.tROLE>("Roles").EntityType.HasKey(o => o.IDRole).HasMany(t => t.tROLE_AUTHORIZATION);
modelBuilder.EntitySet<WMCommon.DAL.EF.tLOOKUP>("Lookups").EntityType.HasKey(o => o.IDLookup).HasMany(t => t.tROLE_AUTHORIZATION);
modelBuilder.EntitySet<WMCommon.DAL.EF.tROLE_AUTHORIZATION>("RoleAuthorizations").EntityType.HasKey(o => o.IDRoleAuthorization);
config.Routes.MapODataRoute("odata", "odata", modelBuilder.GetEdmModel());
config.EnableQuerySupport();
}
}
WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.Routes.MapHttpRoute( //MapHTTPRoute for controllers inheriting ApiController
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
GlobalConfiguration.Configuration.Formatters
.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
}
}
Global.asax:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(config =>
{
ODataConfig.Register(config);
WebApiConfig.Register(config);
});
}
}