OData no http resource was found that matches the request uri - c#

I'm having some trouble getting my Odata service working with my MVC 4 site.
Can you help me figure out what I'm doing wrong?
Here is my WebApiConfig:
public static class WebApiConfig
{
/// <summary>
/// The Register method
/// </summary>
/// <param name="config">The config.</param>
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("odata", "odata", GetEdmModel(), new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "ThisClassesNamespace";
builder.ContainerName = "DefaultContainer";
builder.EntitySet<QueryRequest>("QueryRequests");
var edmModel = builder.GetEdmModel();
return edmModel;
}
}
here is the global.asax's application_start method:
protected void Application_Start()
{
Application["Name"] = "Administration Web Console";
GlobalConfiguration.Configure(WebApiConfig.Register);
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
Error += MvcApplication_Error;
var maplayerDao = new MapLayerDao();
maplayerDao.UpdateProvisioningData();
var factory = new CustomControllerFactory();
ControllerBuilder.Current.SetControllerFactory(factory);
}
finally, here is what the controler's get method looks like
public class QueryRequestsController : ODataController
{
private static ODataValidationSettings _validationSettings = new ODataValidationSettings();
private QueryRequestDao dao = new QueryRequestDao();
// GET: odata/QueryRequests
[HttpGet]
public IHttpActionResult GetQueryRequests()
{
// validate the query.
try
{
// queryOptions.Validate(_validationSettings);
}
catch (ODataException ex)
{
return BadRequest(ex.Message);
}
return Ok<IEnumerable<QueryRequest>>(dao.GetQueryRequests());
}
I can get the metadata just fine at http://10.78.14.177:8040/odata/
which gives me:
{
"#odata.context":"http://10.78.14.177:8040/odata/$metadata","value":[
{
"name":"QueryRequests","kind":"EntitySet","url":"QueryRequests"
}
]
}
but when I go to http://10.78.14.177:8040/odata/QueryRequests
I get No HTTP resource was found that matches the request URI 'http://10.78.14.177:8040/odata/QueryRequests'.
I think the MVC routing is getting this request and it is not going to the OData service... but I'm not sure what the fix is.
Any ideas?

Related

An error occurred when trying to create a controller of type. Make sure that the controller has a parameterless public constructor

I have integrated dependency injection with code first entity framework. But when I call method of api that time I am getting error.
System.InvalidOperationException: An error occurred when trying to create a controller of type 'DrugController'. Make sure that the controller has a parameterless public constructor.
stackTrace:
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()
innerException:
System.ArgumentException: Type 'SA_Admin.Controllers.DrugController' does not have a default constructor
stackTrace:
at System.Linq.Expressions.Expression.New(Type type)
at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
but when I remove constructor with parameter then I can call with out error.
I cross check every file but does not getting error. What I am missing? Also all method of account working perfectly because controller is with out constructor.
Project is only web api project. I did not add mvc.
I used Autofac DI nuget package.
suggest me solution.
Startup.cs
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
IoCConfig.RegisterDependencies();
}
IOCConfig.cs
public class IoCConfig
{
/// <summary>
/// For more info see
/// :http://docs.autofac.org/en/latest/integration/mvc.html
/// </summary>
public static void RegisterDependencies()
{
#region Create the builder
var builder = new ContainerBuilder();
#endregion
#region Register all authentication dependencies
// REGISTER DEPENDENCIES
builder.RegisterType<SA_AdminEntities>().AsSelf().InstancePerRequest();
#endregion
#region Register all web api controllers for the assembly
builder.RegisterApiControllers(typeof(Startup).Assembly);
#endregion
#region Register modules
builder.RegisterModule(new BusinessLogicInstaller());
builder.RegisterModule(new ReposInstaller());
builder.RegisterModule(new ServicesInstaller());
#endregion
#region Set the dependency resolver to be Autofac
var container = builder.Build();
//for WebApi
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
#endregion
}
}
ServiceInstaller.cs
public class ServicesInstaller : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(DrugService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerRequest();
}
}
RepoInstaller.cs
public class ReposInstaller : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
builder.RegisterType<DbFactory>().As<IDbFactory>().InstancePerRequest();
#region Repositories
builder.RegisterAssemblyTypes(typeof(DrugRepo).Assembly)
.Where(t => t.Name.EndsWith("Repo"))
.AsImplementedInterfaces().InstancePerRequest();
#endregion
}
}
BusinessLogicInstaller.cs
public class BusinessLogicInstaller : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ParamEncryption>().As<IParamEncryption>().InstancePerRequest();
builder.RegisterType<Mapper>().As<IMapper>().InstancePerRequest();
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.Filters.Add(new ErrorLogApiHandler());
var constraintResolver = new DefaultInlineConstraintResolver
{
ConstraintMap = { ["apiVersion"] = typeof(ApiVersionRouteConstraint) }
};
//removes xml formatter
config.Formatters.Remove(config.Formatters.XmlFormatter);
//enable json formatters
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
//TODO Turn off move to dev and prod
//enable cors
//config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
//add api versioning
ApiVersion apiVersion = new ApiVersion(1, 0);
config.AddApiVersioning(options =>
{
//will return the headers "api-supported-versions" and "api-deprecated-versions"
options.ReportApiVersions = true;
//TODO define api controllers here with versions
//Like below Example
options.Conventions.Controller<DrugController>().HasApiVersion(apiVersion);
});
//enable attribute routing
config.MapHttpAttributeRoutes(constraintResolver);
}
}
ApiController
[ApiVersion("1.0")]
[RoutePrefix("api/v{version:apiVersion}/Public")]
public class DrugController : ApiController
{
#region Variables
private readonly IDrugService drugService;
private readonly IPersistService persistService;
private readonly IParamEncryption encryptor;
#endregion
#region Constructor
public DrugController(IDrugService drugService, IPersistService persistService, IParamEncryption encryptor)
{
this.drugService = drugService;
this.persistService = persistService;
this.encryptor = encryptor;
}
[Route("Drugs")]
[HttpGet]
public IHttpActionResult Drugs()
{
HttpResponseMessage resp;
try
{
var lstdrugs = drugService.GetDrugs(xx => true);
resp = Request.CreateResponse(HttpStatusCode.OK, lstdrugs);
}
catch (Exception ex)
{
resp = Request.CustomHandleError(ex);
}
return ResponseMessage(resp);
}
#endregion
}
This is the same error you would get if you didn't register one of the parameters with the autofac container. The actual error may be several layers of inner exceptions down. I only see IParamEncryption registered. Register the other two interfaces.
See this link to solve your problem, mainly it is DI registration and resolver problem.
See the below post for more information.
https://stackoverflow.com/a/24257085/7404931

Adding Unity to MVC WebApi app

Hello all I've Added Unity to an MVC app. it seems that DI is working with the MVC Portion of the app but i cannot figure out why it wont work with the API part of the application.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name:"DefaultApi",
routeTemplate:"api/{controller}/{id}",
defaults: new {id = RouteParameter.Optional}
);
config.Formatters.Add(new JsonMediaTypeFormatter());
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
config.Formatters.JsonFormatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None;
}
}
public static class UnityMvcActivator
{
/// <summary>
/// Integrates Unity when the application starts.
/// </summary>
public static void Start(HttpConfiguration configuration)
{
FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(UnityConfig.Container));
DependencyResolver.SetResolver(new UnityDependencyResolver(UnityConfig.Container));
// TODO: Uncomment if you want to use PerRequestLifetimeManager
// Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
}
/// <summary>
/// Disposes the Unity container when the application is shut down.
/// </summary>
public static void Shutdown()
{
UnityConfig.Container.Dispose();
}
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Address", action = "Index", id = UrlParameter.Optional }
);
}
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
BundleConfig.RegisterBundles(BundleTable.Bundles);
RouteConfig.RegisterRoutes(RouteTable.Routes);
UnityMvcActivator.Start(GlobalConfiguration.Configuration);
}
I am just trying to get a repository to work in the web api but i keep getting
An error occurred when trying to create a controller of type 'AddressSearchController'. Make sure that the controller has a parameterless public constructor.",
this error. I have seen a few posts about this. I have tried them and still cannot get this to work. Does anyone have any suggestions?
public class AddressSearchController:_SimpleController<Address>
{
public AddressSearchController(IRepository<Address> addressRepository) : base(addressRepository)
{
}
[HttpPost]
[Route("api/AddressSearch/Search")]
public IHttpActionResult Search([FromBody] AddressSearchDto addressSearchDto)
{
var addresses = new List<Address>()
{
CreateAddress(1,"Main St", 123),
CreateAddress(2,"Main St", 124),
CreateAddress(3,"Main St", 125),
};
return Ok(addresses);
}
static Address CreateAddress(int id,string street, int houseNumber)
{
return new Address()
{
Id = id,
StreetName = street,
HouseNumber = houseNumber
};
}
}
public static void RegisterTypes(IUnityContainer container)
{
RegisterInstances(container);
}
private static void RegisterInstances(IUnityContainer container)
{
container.RegisterType<IAddressContext, AddressContext>();
container.RegisterType(typeof(IRepository<>), typeof(Repository<>));
}
using System;
using Address_Tracker.Data.Context;
using Address_Tracker.Data.Context.Interfaces;
using Address_Tracker.Data.Repositories;
using Unity;
namespace Address_Tracker
{
/// <summary>
/// Specifies the Unity configuration for the main container.
/// </summary>
public static class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
/// <summary>
/// Configured Unity Container.
/// </summary>
public static IUnityContainer Container => container.Value;
#endregion
/// <summary>
/// Registers the type mappings with the Unity container.
/// </summary>
/// <param name="container">The unity container to configure.</param>
/// <remarks>
/// There is no need to register concrete types such as controllers or
/// API controllers (unless you want to change the defaults), as Unity
/// allows resolving a concrete type even if it was not previously
/// registered.
/// </remarks>
public static void RegisterTypes(IUnityContainer container)
{
RegisterInstances(container);
}
private static void RegisterInstances(IUnityContainer container)
{
container.RegisterType<IAddressContext, AddressContext>();
container.RegisterType(typeof(IRepository<>), typeof(Repository<>));
}
}
}
Your AddressSearchController should have a default constructor, or if you have no default / parameterless constructor, then please pass interfaces instead of concrete classes in AddressSearchController.
I believe you may have such a scenario:
public class AddressSearchController : ApiController
{
public AddressSearchController(SomeClassParameter obj)
{
//some code
}
}
What you want actually is ether this:
public class AddressSearchController : ApiController
{
public AddressSearchController() // add default ctor
{
}
public AddressSearchController(SomeClassParameter obj)
{
//some code
}
}
or this:
Register the interface ISomeClassParameter for type SomeClassParameter in Unity
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
UnityConfig.RegisterComponents(); // <----- Add this line
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
Register component:
container.RegisterType<ISomeClassParameter , SomeClassParameter >();
and do constructor injection :
public class AddressSearchController : ApiController
{
public AddressSearchController(ISomeClassParameter obj)
{
//some code
}
}
Also, make sure you have the WebApi version of Unity
I tried it out, it worked for me:

HTTP Error 404.0 - Not Found WebAPI

I am create a authentication WebAPI.
in APP_Start Folder:
WebApiConfig file:
public static string UrlPrefix
{
get
{
return "api";
}
}
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute("DefaultApi", string.Concat(WebApiConfig.UrlPrefix, "/{controller}/{id}"), new { id = RouteParameter.Optional });
MediaTypeHeaderValue mediaTypeHeaderValue = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault<MediaTypeHeaderValue>((MediaTypeHeaderValue t) => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(mediaTypeHeaderValue);
}
public static void Login(HttpConfiguration config)
{
config.Routes.MapHttpRoute("Login", string.Concat(WebApiConfig.UrlPrefix, "/{controller}/{username}/{password}"), new { username = RouteParameter.Optional, password = RouteParameter.Optional });
MediaTypeHeaderValue mediaTypeHeaderValue = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault<MediaTypeHeaderValue>((MediaTypeHeaderValue t) => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(mediaTypeHeaderValue);
}
WebApiApplication file:
protected void Application_PostAuthorizeRequest()
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
WebApiConfig.Login(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
I have the AuthenticationController in my controllers folder. I am not sure what am I doing wrong. I have a few other APIs that work fine.
In the url I put the url I am entering is localhost:4453/api/authentication/user/pass. if I only enter user I get.
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:4453/api/Authentication/asas/'.","MessageDetail":"No action was found on the controller 'Authentication' that matches the request."}
AuthenticationController:
public bool Get(string username, string password)
{
return true;
}
Global.aspx had to have this line
WebApiConfig.Login(GlobalConfiguration.Configuration);

Global Json Formatting not working

I have a problem with my json formatting. I want that my json result is in camel case. I use the following code to achieve this (ASP.NET WebAPI Tip #3: camelCasing JSON):
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var formatters = config.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var settings = jsonFormatter.SerializerSettings;
settings.Formatting = Newtonsoft.Json.Formatting.Indented;
settings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
}
}
The Register method is called from the Global.asax:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// Load all modules
ESP.Framework.Web.ModuleManager.Singleton.LoadModules(System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath);
// Virtual Path Provider
// // System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new Provider.ESPPathProvider());
// Register Controller-Factory
//ControllerBuilder.Current.SetControllerFactory(typeof(ESP.Web.Controllers.DynamicActionControllerFactory));
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
}
But when i call the following method in my browser:
[HttpGet]
public JsonResult GetUser()
{
var user = ESP.Framework.Web.Security.UserManager.GetAllUser();
return Json(user, JsonRequestBehavior.AllowGet);
}
The result is not in camel case (The assembly in which the method is, is dynamically loaded).
Maybe someone has an solution. Thank you!
I think you are mixed mvc website and WebApi,In asp.net current version, they use different api. You set the WebApi config, but use the Mvc website's api. You should use WebApi version:
using System.Web.Http;
public class UserController : ApiController
{
[Route("user/getalluser")]
public IEnumerable<User> Get()
{
return GetAllUser();
}
}
Then you can access url 'user/getalluser' to get json data
Ok, i found a solution:
public string SerializeObject(object toSerialize)
{
var settings = new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(), Formatting = Formatting.Indented };
return JsonConvert.SerializeObject(toSerialize, Formatting.None, settings);
}
[HttpGet]
public string GetUser()
{
var user = Simplic.Framework.Web.Security.UserManager.GetAllUser();
return SerializeObject(user);
}
But it is not very nice...

WebAPI and ODataController return 406 Not Acceptable

Before adding OData to my project, my routes where set up like this:
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
All controllers provide Get, Put (action name is Create), Patch (action name is Update) and Delete. As an example, the client uses these various standard url's for the CustomerType requests:
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";
Then I added an OData controller with the same action names as my other Api controllers. I also added a new route:
ODataConfig odataConfig = new ODataConfig();
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: odataConfig.GetEdmModel()
);
So far I changed nothing on the client side. When I send a request, I get a 406 Not Available error.
Are the routes getting mixed up? How can I solve this?
If you are using OData V4, replace using System.Web.Http.OData;
With using Microsoft.AspNet.OData; (Please check the comments for the latest library)
in the ODataController works for me.
The order in which the routes are configured has an impact. In my case, I also have some standard MVC controllers and help pages. So in 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);
}
The filter and routeTable parts weren't there when I started my project and are 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 }
);
}
And as a bonus, here's my 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 }
);
}
This has to be in that EXACT ORDER. I tried moving the calls around and ended up with either MVC, Api or Odata broken with 404 or 406 errors.
So I can call:
localhost:xxx/ -> leads to help pages (home controller, index page)
localhost:xxx/api/ -> leads to the OData $metadata
localhost:xxx/api/Sites -> leads to the Get method of my SitesController inheriting from ODataController
localhost:xxx/api/Test -> leads to the Get method of my TestController inheriting from ApiController.
Set routePrefix to "api".
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<CustomerType>("CustomerType");
config.MapODataServiceRoute(routeName: "ODataRoute", routePrefix: "api", model: builder.GetEdmModel());
Which OData version are you using? Check for correct namespaces, for OData V4 use System.Web.OData, for V3 System.Web.Http.OData. Namespaces used in controllers have to be consistent with the ones used in WebApiConfig.
My issue was related to returning the entity model instead of the model I exposed (builder.EntitySet<ProductModel>("Products");). Solution was to map entity to resource model.
Another thing to be taken into consideration is that the URL is case sensitive so:
localhost:xxx/api/Sites -> OK
localhost:xxx/api/sites -> HTTP 406
The problem I had was that i had named my entityset "Products" and had a ProductController. Turns out the name of the entity set must match your controller name.
So
builder.EntitySet<Product>("Products");
with a controller named ProductController will give errors.
/api/Product will give a 406
/api/Products will give a 404
So using some of the new C# 6 features we can do this instead:
builder.EntitySet<Product>(nameof(ProductsController).Replace("Controller", string.Empty));
None of the excellent solutions on this page worked for me. By debugging, I could see that the route was getting picked up and the OData queries were running correctly. However, they were getting mangled after the controller had exited, which suggested that it was the formatting that was generating what appears to be the OData catch-all error: 406 Not Acceptable.
I fixed this by adding a custom formatter based on the Json.NET library:
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}));
}
}
Then in WebApiConfig.cs, I added the line config.Formatters.Insert(0, new JsonDotNetFormatter()). Note that I am sticking closely to the order described in Jerther's answer.
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());
}
}
The problem/solution in my case was even more stupid. I'd left test code in my action that returned a completely different model type, just a Dictionary, and not my proper EDM model type.
Though I protest that the use of HTTP 406 Not Acceptable to communicate the error of my ways, is equally as stupid.
My error and fix was different from the answers above.
The specific issue I had was accessing a mediaReadLink endpoint in my ODataController in WebApi 2.2.
OData has a 'default stream' property in the spec which allows a returned entity to have an attachment. So the e.g. json object for filter etc describes the object, and then there is a media link embedded which can also be accessed. In my case it is a PDF version of the object being described.
There's a few curly issues here, the first comes from the config:
<system.web>
<customErrors mode="Off" />
<compilation debug="true" targetFramework="4.7.1" />
<httpRuntime targetFramework="4.5" />
<!-- etc -->
</system.web>
At first I was trying to return a FileStreamResult, but i believe this isn't the default net45 runtime. so the pipeline can't format it as a response, and a 406 not acceptable ensues.
The fix here was to return a HttpResponseMessage and build the content manually:
[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;
}
My last issue here was getting an unexpected 500 error after returning a valid result. After adding a general exception filter I found the error was Queries can not be applied to a response content of type 'System.Net.Http.StreamContent'. The response content must be an ObjectContent.. The fix here was to remove the [EnableQuery] attribute from the top of the controller declaration, and only apply it at the action level for the endpoints that were returning entity objects.
The [System.Web.Http.Route("myobjdownload")] attribute is how to embed and use media links in OData V4 using web api 2.2. I'll dump the full setup of this below for completeness.
Firstly, in my 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);
}
The end result is my json objects get these odata properties embedded:
odata.mediaContentType=application/pdf
odata.mediaReadLink=http://myhost/data/myobj/%3fid%3dmyid/content
And the following the decoded media link http://myhost/data/myobj/?id=myid/content fires the endpoint on your MyObjController : ODataController.
Found in the GitHub error: "Unable to use odata $select, $expand, and others by default #511", their solution is to put the following line BEFORE registering the route:
// enable query options for all properties
config.Filter().Expand().Select().OrderBy().MaxTop(null).Count();
Worked like a charm for me.
Source: https://github.com/OData/RESTier/issues/511
In my case I needed to change a non-public property setter to public.
public string PersonHairColorText { get; internal set; }
Needed to be changed to:
public string PersonHairColorText { get; set; }
In my case (odata V3) I had to change name of OdataController to be same as provided in
ODataConventionModelBuilder and that solved the issue
my controller:
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);
});
}
}
For me the problem was, that I used LINQ and selected the loaded objects directly.
I had to use select new for it to work:
return Ok(from u in db.Users
where u.UserId == key
select new User
{
UserId = u.UserId,
Name = u.Name
});
This did not work:
return Ok(from u in db.Users
where u.UserId == key
select u);

Categories

Resources