I'm trying to create an owin self host webapi with odata support.
I added all the dependencies and set everything up, when i call the get method (just by surfing to it with chrome).
The breakpoint i put on the GET gets called and returns without an exception.
But chrome just returns a blank screen, no http errors are found in the console of chrome.
When i do this in IE10 (yes old version, but i am not allowed to update it) i get 406 (Not Acceptable).
This is my Startup.cs code :
public void Configuration(IAppBuilder appBuilder)
{
// Configure Web API for self-host.
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
//config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml"));
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel());
appBuilder.UseWebApi(config);
}
This is my controller code, just a simple GET.
public class ProductsController : ODataController
{
ProductsContext db = new ProductsContext();
[EnableQuery]
public IQueryable<Product> Get()
{
try
{
return db.Products;
}
catch (System.Exception ex)
{
string x = "y";
return db.Products;
}
}
}
db.Products is an empty table for now, but this should still return an empty array right ?
Any help is greatly appreciated!
Thanks.
Thanks Fan Ouyang for the link, this has helped me resolve my issue!
Now i'm using the following code :
Owin Startup
var config = new HttpConfiguration();
config.MapODataServiceRoute(routeName: "OData", routePrefix: "odata", model: GetEdmModel());
appBuilder.UseWebApi(config);
GetEdmModel Function
private IEdmModel GetEdmModel()
{
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customers");
return modelBuilder.GetEdmModel();
}
Controller GET
public PageResult<Customer> Get(ODataQueryOptions<Customer> queryOptions)
{
IQueryable results = queryOptions.ApplyTo(CustomerList.AsQueryable());
return new PageResult<Customer>(results as IEnumerable<Customer>, Request.ODataProperties().NextLink, CustomerList.Count);
}
Related
As a convention in our project, we have named all our API controller classes as AuthenticationApiController as opposed to controller's in MVC AuthenticationController.
But now when API gets invoked, we have to call it like /api/authenticationapi/logout.
Thought not a problem, but I am not liking that "api" word is coming twice in the URL.
Is there a way, I can customize the route which is defined as [Route("api/[controller]")] to remove api from controller name when URL is getting added to route table.
**Note: looking for a generic way, rather than hardcoding the name on every api controller.
Try to use Url Rewriter and refer to my demo which uses asp.net core 3.0:
1.Create RewriterRules
public class RewriteRules
{
public static void ReWriteRequests(RewriteContext context)
{
var request = context.HttpContext.Request;
var path = request.Path.Value;
if (path != null)
{
var array = path.Split("/");
if (array[1] == "api" && !array[2].EndsWith("api", StringComparison.OrdinalIgnoreCase))
{
array[2] = array[2] + "api";
var newPath = String.Join("/", array);
context.HttpContext.Request.Path = newPath;
}
}
}
}
2.Register it in startup Configure method(before app.UseMvc() if you use core 2.2)
app.UseRewriter(new RewriteOptions()
.Add(RewriteRules.ReWriteRequests)
);
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
3.Test
[Route("api/[controller]")]
[ApiController]
public class AuthenticationApiController : ControllerBase
{
// GET: api/TestApi
[HttpGet("logout")]
public IEnumerable<string> logout()
{
return new string[] { "value1", "value2" };
}
}
Call /api/authentication/logout it will comes into the action successfully
How can I redirect Action which is not found in controller into another action within the same controller? Let's say that file abc.txt is requested via http://localhost:5000/Link/GetFile/abc.txt. My controller correctly serving that file. But now, i need to handle request such as http://localhost:5000/Link/Document/abc. Of course there is no any action matched to Document so I need to invoke function Error within the same controller (including id from original request).
I tried to solve this with StatusCodePagesWithReExecute function but then my File action is not working (each request goes directly to Error function).
I have following controller:
public class LinkController : ControllerBase
{
public IActionResult GetFile(string id)
{
return DownloadFile(id);
}
public IActionResult Error(string id)
{
return File("~/index.html", "text/html");
}
private FileResult DownloadFile(string fileName)
{
IFileProvider provider = new PhysicalFileProvider(#mypath);
IFileInfo fileInfo = provider.GetFileInfo(fileName);
var readStream = fileInfo.CreateReadStream();
return File(readStream, "text/plain");
}
}
and startup configuration:
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "application/octet-stream",
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{id?}"
);
});
Any clues how to solve this problem?
Regards
You can use UseStatusCodePages to achieve a simple redirection whenever there's a 404. Here's what it looks like:
app.UseStatusCodePages(ctx =>
{
if (ctx.HttpContext.Response.StatusCode == 404)
ctx.HttpContext.Response.Redirect("/Path/To/Your/Action");
return Task.CompletedTask;
});
Just add this somewhere above UseMvc.
EDIT:
I´m sorry, my first answer was not correct.
IRouteCollection router = RouteData.Routers.OfType<IRouteCollection>().First();
with this, you can match an url to controller action
Create HttpContext for testing (example with injection)
private readonly IHttpContextFactory _httpContextFactory;
public HomeController(
IHttpContextFactory httpContextFactory)
{
_httpContextFactory = httpContextFactory;
}
Create the context with values
HttpContext context = _httpContextFactory.Create(HttpContext.Features);
context.Request.Path = "/Home/Index";
context.Request.Method = "GET";
Check route
var routeContext = new RouteContext(context);
await router.RouteAsync(routeContext);
bool exists = routeContext.Handler != null;
Further reading: https://joonasw.net/view/find-out-if-url-matches-action
I am going to create an Odata api in asp.net mvc 4 for get data from new table. when I call the Odata method and use debug in the code It shows me data properly. But when it comes to browser, it shows empty screen.
There is no error shown in the code.
this is my Odata method :
[Queryable]
public HCPData GetHCPData([FromODataUri] int key)
{
// return SingleResult.Create(db.HCPDatas.Where(hcpdata => hcpdata.Id == key));
IQueryable<HCPData> result = db.HCPDatas.Where(p => p.CompanyId == key);
return result.FirstOrDefault();
}
this is my WebApiConfig method:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
//var entitySetConfiguration1 = modelBuilder.EntitySet<Job>("Job");
var entitySetConfiguration1 = modelBuilder.EntitySet<HCPData>("HCPData");
var customer = modelBuilder.EntityType<HCPData>();
modelBuilder.EntitySet<HCPData>("HCPData");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: modelBuilder.GetEdmModel());
}
When I checked the console of empty screen in browser it shows an error: "NetworkError: 406 Not Acceptable - http://localhost:50369/HCPData?key=11"
Please let me know the solution of the issue. Thanks in advance.
What's the result if you change the controller as follows:
public class HCPDataController : ODataController
{
[EnableQuery]
public HCPData GetHCPData([FromODataUri] int key)
{
...
}
}
[My Sample]
Because, at my side, if I implement the controller as follows, it can work:
[EnableQuery]
public HCPData GetHCPData([FromODataUri] int key)
{
var data = new HCPData
{
CompanyId = 2,
Name = "Key = " + key
};
return data;
}
Example:
Let me issue the following request:
I can get the following response:
{
"#odata.context":"http://localhost:62591/odata/$metadata#HCPData/$entity","CompanyId":2,"Name":"Key = 11"
}
I'm using RestSharp to consume my WebApi. Here is the relevant code:
var insertRequest = new RestRequest("MappedSystem", Method.POST);
insertRequest.AddBody(new MappedSystemCreateModel
{
MappedSystemDetails = new MappedSystemCreateModel.Details
{
SystemName = "TestName",
SystemVersion = "TV"
}
});
var response = RestClient.Execute(insertRequest);
But when I debug my WebApi it hits the Get() method:
public class MappedSystemController : ApiController
{
private readonly IMappedSystemService _mappedSystemService;
public MappedSystemController(IMappedSystemService mappedSystemService)
{
_mappedSystemService = mappedSystemService;
}
public MappedSystemViewModel[] Get()
{
=> return _mappedSystemService.Get();
}
public MappedSystemViewModel Get(Guid id)
{
return _mappedSystemService.Get(id);
}
[HttpPost]
public MappedSystemViewModel Post([FromBody]MappedSystemCreateModel model)
{
return _mappedSystemService.Post(model);
}
}
I think there must be something wrong with my routeConfig, but I don't know at this point:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{id2}/",
defaults: new { id = RouteParameter.Optional, id2 = RouteParameter.Optional }
);
There was a Post route handler interceptor checking to see if I had correctly added a trailing '/'. Since I hadn't it responded with a 301 and redirected. Somehow/somewhere in the RedirectPermanently() the Verb was getting lost. The answer was to originally make the request with the trailing '/'. But of course this does shed light on the error found in the redirect.
Also, I needed this following code on the request or the body wouldn't deserialize:
insertRequest.RequestFormat = DataFormat.Json;
I am relatively new to Web Api and I am having trouble POSTing a Person object. If I run in debug, I see that my uriString never gets set and I don't understand why. Because of this, I get "400 Bad Request" errors in Fiddler for all attempted Posts.
I have tried replicating what others have done when it comes to the Post action. Every example I've found uses a repository to add the person to the database. I do not have repositories however, but instead am using the NHibernate Save method to carry out this functionality. Here are the domain class, mapping by code file, WebApiConfig, and the PersonController.
public class Person
{
public Person() { }
[Required]
public virtual string Initials { get; set; }
public virtual string FirstName { get; set; }
public virtual char MiddleInitial { get; set; }
public virtual string LastName { get; set; }
}
public class PersonMap : ClassMapping<Person>
{
public PersonMap()
{
Table("PERSON");
Lazy(false);
Id(x => x.Initials, map => map.Column("INITIALS"));
Property(x => x.FirstName, map => map.Column("FIRST_NAME"));
Property(x => x.MiddleInitial, map => map.Column("MID_INITIAL"));
Property(x => x.LastName, map => map.Column("LAST_NAME"));
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.Services.Replace(typeof(IHttpActionSelector), new HybridActionSelector());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{action}/{actionid}/{subaction}/{subactionid}",
defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional,
actionid = RouteParameter.Optional, subaction = RouteParameter.Optional, subactionid = RouteParameter.Optional }
);
config.BindParameter( typeof( IPrincipal ), new ApiPrincipalModelBinder() );
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing();
}
}
public class PersonsController : ApiController
{
private readonly ISessionFactory _sessionFactory;
public PersonsController (ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
// POST api/persons
[HttpPost]
public HttpResponseMessage Post(Person person)
{
var session = _sessionFactory.GetCurrentSession();
using (var tx = session.BeginTransaction())
{
try
{
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
var result = session.Save(person);
var response = Request.CreateResponse<Person>(HttpStatusCode.Created, person);
string uriString = Url.Route("DefaultApi", new { id = person.Initials });
response.Headers.Location = new Uri(uriString);
tx.Commit();
return response;
}
catch (Exception)
{
tx.Rollback();
}
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
}
}
Fiddler information:
POST //localhost:60826/api/employees HTTP/1.1
Request Headers:
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:xxxxx
Content-Length: 71
Request Body:
{
"Initials":"MMJ",
"LastName":"Jordan",
"FirstName":"Michael"
}
This line never sets the uriString to the correct value. string uriString = Url.Route("DefaultApi", new { id = person.Initials });
I've also tried using Url.Link instead of Url.Route. I've tried adding the controller = "Persons" inside the 'new' block, but that had no effect. Why isn't uriString being set? I'll listen to any thoughts at this point.
EDIT
I have tried
string uriString = Url.Link("DefaultApi", new { controller = "Persons", id = person.Initials, action="", actionid="", subaction="", subactionid="" });
as well as using a separate routeconfig
config.Routes.MapHttpRoute(
name: "PostApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional
} );
with
string uriString = Url.Link("PostApi", new { controller = "Persons", id = person.Initials});
and have had no luck.
SOLUTION
I was able to get this Post to work by using the line of code below. I'm not entirely sure if this is the correct way to do it, so if anybody knows differently, please share. Otherwise, I will happily use this approach.
response.Headers.Location = new Uri(this.Request.RequestUri.AbsoluteUri + "/" + person.Initials);
Problem seems to be here:
string uriString = Url.Route("DefaultApi", new { id = person.Initials });
You are only passing id while you need to be passing other parameters such as controller, etc.
You may construct URL this way:
string uriString = Url.Action("ActionName", "ControllerName", new { Id = person.Initials });