OData v4 on .Net Core 1.1 missing /$metadata - c#

Using .net Core 1.1, with the Microsoft.AspNetCore.OData libraries, I am able to get an OData endpoint working with my simple controller to perform get, $expand, and other queries. However, I can't get it to return the $metadata to be returned. This question ($Metadata with WebAPi OData Attribute Routing Not Working) is for the same problem, however the .Net APIs have changed since this was posted.
Is there a setting, flag, or something else I need to enable?
This (http://localhost:52315/odata) seems to return the meta data,
{
"#odata.context":"http://localhost:52315/odata/$metadata","value":[
{
"name":"Users","kind":"EntitySet","url":"Users"
},{
"name":"HelloComplexWorld","kind":"FunctionImport","url":"HelloComplexWorld"
}
]
}
this (http://localhost:52315/odata/$metadata) gives me the error:
An unhandled exception occurred while processing the request.
NotSupportedException: No action match template '$metadata'
in 'MetadataController'
Microsoft.AspNetCore.OData.Routing.Conventions.DefaultODataRoutingConvention.SelectAction(RouteContext routeContext)
My Startup.cs looks like this:
public void Configure(IApplicationBuilder app) {
app.UseDeveloperExceptionPage();
app.UseOData("odata");
app.UseMvcWithDefaultRoute();
}
public void ConfigureServices(IServiceCollection services) {
services.AddMvc().AddWebApiConventions();
services.AddSingleton<ISampleService, ApplicationDbContext>();
services.AddOData<ISampleService>(builder =>
{
builder.Namespace = "Sample";
builder.EntityType<ApplicationUser>();
builder.EntityType<Product>();
builder.Function("HelloComplexWorld").Returns<Permissions>();
});
}
NOTE: I can work around it by adding this at the start of my ConfigureServices(...) method, though it seems wrong given $metadata support should be part of the core platform.
app.Use(async (context, next) => {
if (0 == string.Compare(context.Request.Path, #"/odata/$metadata", true)) {
context.Request.Path = "/odata";
}
await next.Invoke();
});

I revisited this today and had more success using the Microsoft.AspNetCore.OData.vNext 6.0.2-alpha-rtm package. Referencing this example, the metadata and default OData routing worked as expected. My minimal configuration is:
public void ConfigureServices(IServiceCollection services)
{
services.AddOData();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Document>("Documents");
app.UseMvc(builder =>
{
builder.MapODataRoute("odata", modelBuilder.GetEdmModel());
});
}

The /odata/$metadata route should return "an XML representation of the service’s data model" (EDMX) (according to the OData v4 Web API documentation). This is not the same as the service root /odata/, which returns the top level description of resources published by the OData service (as shown in your example).
I encountered the same issue using the pre-release Microsoft.AspNetCore.OData 1.0.0-rtm-00015, since an official release is not yet available (see open issue on OData Web API repo).
To illustrate the point, you could manually emit the metadata, as in the crude example below. (You can find the InMemoryMessage class in the OData/odata.net GitHub repo.)
However, I would suggest waiting for an official release of OData ASP.NET Core as, quoting from the above issue, the "branch is still in its early stages and we may take a different approach once we have our architecture finalized". So things may well change... and $metadata should definitely work "out of the box"!
app.Use((context, func) =>
{
if (context.Request.Path.StartsWithSegments(new PathString("/data/v1/$metadata"), StringComparison.OrdinalIgnoreCase))
{
var model = app.ApplicationServices.GetService<IEdmModel>();
MemoryStream stream = new MemoryStream();
InMemoryMessage message = new InMemoryMessage() {Stream = stream};
ODataMessageWriterSettings settings = new ODataMessageWriterSettings();
ODataMessageWriter writer = new ODataMessageWriter((IODataResponseMessage)message, settings, model);
writer.WriteMetadataDocument();
string output = Encoding.UTF8.GetString(stream.ToArray());
return context.Response.WriteAsync(output);
}
return func();
});

Related

Why is localhost next js GetStaticProps returning 500 error against localhost C# ASP.NET Core API route

I have a C# / ASP.NET Core Web API project running on https://localhost:7001 and a next js app running on http://localhost:3000.
I can run the C# API from swagger and directly in the browser (https://localhost:7001/api/SourceSystems), but when I try to call it from the next js page using GetStaticProps, I get a 500 error.
Next.js code:
export default function Sourcesystem({systems}) {
return (
<ul>
{systems.map((system) => (
<li key={system.systemName}>{system.systemName} </li>
))}
</ul>
)
};
// This function gets called at build time
export async function getStaticProps() {
// Call an external API endpoint to get posts
const res = await fetch(
'https://localhost:7001/api/Schedules',
{
method:'GET',
}
)
const systems = await res.json()
console.log(systems);
return {
props: {
systems
},
}
}
I have added CORS to the c# code (I think) in
program.cs
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins(
"http://example.com",
"http://www.contoso.com",
"http://localhost:3000"
);
});
});
// services.AddResponseCaching();
builder.Services.AddControllers();
// add dbContext
builder.Services.AddDbContext<GdqcDevContext>(options => options.UseSqlServer("Data Source = RAZERPRO17; Initial Catalog = GDQC_dev; Integrated Security = True; Connect Timeout = 30; Encrypt = False; TrustServerCertificate = False; ApplicationIntent = ReadWrite; MultiSubnetFailover = False"));
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.MapControllers();
app.Run();
Next is saying the the fetch failed:
I am suspecting something still not set correctly for CORS but I have copied the CORS configuration and the middleware assignment order from documentation and other stackoverflow answers. NOTE: I am running the c# API code using the debug browser rather than directly from IIS. I have read somewhere about the OPTIONS but this is only for the full blown IIS
I have also added a CORS guard annotation to the c# controller with no success
namespace Overwatch_API.Controllers
{
[EnableCors("MyAllowSpecificOrigins")]
[Route("api/[controller]")]
[ApiController]
public class SourceSystemsController : ControllerBase
{
private readonly GdqcDevContext _context;
public SourceSystemsController(GdqcDevContext context)
{
_context = context;
}
// GET: api/SourceSystems
[HttpGet]
public async Task<ActionResult<IEnumerable<SourceSystem>>> GetSourceSystems()
...
UPDATE: It looks like Next is returning the following error message in the logging:
cause: Error: self-signed certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1538:34)
at TLSSocket.emit (node:events:513:28)
at TLSSocket._finishInit (node:_tls_wrap:952:8)
at ssl.onhandshakedone (node:_tls_wrap:733:12) {
code: 'DEPTH_ZERO_SELF_SIGNED_CERT'
I presume this is related to the SSL cert on the .net core 6 api code, as this is being called with https. How do I get next to accept a self signed cert, or build a propertly signed cert for the dev environment
This issue is common in nextjs.
I found two ways to fixed it.
1. If the issue occurs in dev environment. we can use NODE_TLS_REJECT_UNAUTHORIZED=0 to solve it.
① Overcome the DEPTH_ZERO_SELF_SIGNED_CERT on Node.js
② Self Signed SSL Certificate in NextJS
2. We also can use NODE_EXTRA_CA_CERTS to solve it. It can be used in Dev or Prod Environment.
Node TLS socket : DEPTH_ZERO_SELF_SIGNED_CERT error

ASP.NET Web API Stack Trace Not Available - UseProblemDetails

It has been a while since I developed an API so bear with me. I have created a new web API in the new .NET 5.0 framework. I have tried using Hellang.Middleware.ProblemDetails nuget for my error handling middleware. Seems to be working, but I cannot get any stack trace details to show for life the me, is there something I am missing?
I can only get the following details:
{"type":"https://httpstatuses.com/404","title":"Not
Found","status":404,"traceId":"00-02c4e89a990c5745bc4250cfad83d5e3-bb8c1dab98b44a44-00"}
Here is relevant code from my startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<CoreDbContext>(op => op.UseSqlServer(AppSettings.DBConnectionString).UseLazyLoadingProxies());
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
services.AddProblemDetails(opts =>
{
// Control when an exception is included
opts.IncludeExceptionDetails = (ctx, ex) =>
{
// Fetch services from HttpContext.RequestServices
var env = ctx.RequestServices.GetRequiredService<IHostEnvironment>();
return env.IsDevelopment();
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseProblemDetails();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
The returned ProblemDetails is for a 404. This wouldn't have a stack trace associated with it. By the looks of it in production if an exception occurs then you will get a raw 500, whereas in development it should render the stack in the developer exception page. Try introducing an obvious exception and see what is returned.
The following link (though outdated) provides some more details on this: https://andrewlock.net/handling-web-api-exceptions-with-problemdetails-middleware/
Usually the 404 is the error you get because the API can't find the method, try to be sure for the url.
For example
[HttpGet]
[Route("api/AnyNameyouWantForRoute/parameter")]
// your method in controller
Your url must be like
http://theIpYouChoose/api/AnyNameyouWantForRoute/parameter
If you have a typo in this you will not find the method to call and you will get 404

.NET Core 2.0 - Still seeing No 'Access-Control-Allow-Origin' header error

I'm at wit's end on this one. I've already researched other answers to similar questions on SO w/o any luck.
I'm fairly certain I've got CORS enabled correctly to allow incoming requests (in this case, POST requests) from all origins, but I'm seeing the error below:
Failed to load http://localhost:5000/expenses: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:4200' is therefore not allowed
access. The response had HTTP status code 500.
Here's how I've enabled CORS in my webAPI project:
relevant methods in Startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc();
services.AddDbContext<ExpensesDbContext>(options =>
options.UseMySQL(Configuration.GetConnectionString("DefaultConnection")));
services.AddTransient<IBaseDa<Accounts>, AccountsDataAccess>();
services.AddTransient<IExpensesDa, ExpensesDa>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
env.EnvironmentName = "Development";
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(builder => builder
.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()
.AllowCredentials());
app.UseMvc();
}
If i'm using .AllowAnyOrigin() and .AllowAnyMethod(), why am I seeing the error above?
Was scratching my head on this situation here for a while. I had CORS enabled properly, but some calls were still returning the Access-Control-Allow-Origin error. I found the problem... the sneaky sneaky problem...
Our problem was caused by how we were using app.UseExceptionHandler. Specifically, here's the code we were using, except our original code didn't have the context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); line.
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var errorFeature = context.Features.Get<IExceptionHandlerFeature>();
var exception = errorFeature.Error;
var problemDetails = new ProblemDetails
{
Title = R.ErrorUnexpected,
Status = status,
Detail =
$"{exception.Message} {exception.InnerException?.Message}"
};
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
context.Response.StatusCode = problemDetails.Status.GetValueOrDefault();
context.Response.WriteJson(problemDetails, "application/problem+json");
await Task.CompletedTask;
});
});
app.UseExceptionHandler is a much lower level function than controller actions, and thus does not take part in anything related to CORS natively. Adding context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); fixed the problem.
The combination of netCore2.0 (http://localhost:5000/) + Angular (http://localhost:4200) + chrome = Access-Control-Allow-Origin. I have had this issue before and it took me 3 days to realize that chrome will always throw this error. I think it is because chrome views localhost as the origin disregarding the port even tho the middleware explicitly tells it not too especially on POST requests.
I would try and define a policy in your startup.cs Configure services:
public void ConfigureServices(IServiceCollection services)
{
//add cors service
services.AddCors(options => options.AddPolicy("Cors",
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
then in your Configure method I would add that:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//authentication added
app.UseAuthentication();
app.UseCors("Cors");
app.UseMvc();
}
... This most likely wont work and but try it any who.... This drove me mad and I needed the satisfaction of seeing if the request even attempted to hit the asp.netCore backend:
I used
If you really want to see I would clear your cache and cookies then add
IHttpContextAccessor to get low level control of whats going on in the request.
In my dilema with the same problem I needed angular to send an image. I was getting the annyoing
Origin error then through exprimenting I got the Image by injecting IHttpContextAccessor into my controller and
debugging
public void ConfigureServices(IServiceCollection services)
{
//add cors service
services.AddCors(options => options.AddPolicy("Cors",
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
services.AddMvc();
// register an IHttpContextAccessor so we can access the current
// HttpContext in services by injecting it
//---we use to this pull out the contents of the request
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
you want to inject this into whatever controller u are
using to retrieve the json object of the POST request. Im going to use the example as Home controller:
public class HomeController : Controller
{
// make a read only field
private readonly IHttpContextAccessor _httpContextAccessor;
//create ctor for controller and inject it
public UserService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
// now in your post method use this to see what the if anything came in through the request:
public async Task<IActionResult> Picload(IFormFile file){//---always null
// in my problem I was loading and image from.
var file = _httpContextAccessor.HttpContext.Request.Form.Files[0];
}
Using this it gave me access to the image chrome was giving me an Origin error about.
My crystal ball tells me that somewhere in your C# code an error appear in time of execution and this way the "return" statement of the service is never executed. So debug your code and fix the error so the response is returned to the browser.

DotNetCore 1.0 MVC how to automatically redirect to a single domain in live

I have multiple domains for a website:
http://example.com
http://www.example.com
http://www.example.co.uk
In production, I want the primary domain to be http://www.example.com and for all other associated domains to be automatically redirected to the primary.
Historically I would've done this with URLRewrite, however I'm led to believe that doesn't exist in DotNetCore.
So... how would I do this?
Also, I don't want this to affect development environments.
An answer that works with DotNetCore 1.0 (also forces https)
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Other configuration code here...
if (env.IsProduction())
{
app.Use(async (context, next) =>
{
if (context.Request.Host != new HostString("www.example.com"))
{
var withDomain = "https://www.example.com" + context.Request.Path;
context.Response.Redirect(withDomain);
}
else if (!context.Request.IsHttps)
{
var withHttps = "https://" + context.Request.Host + context.Request.Path;
context.Response.Redirect(withHttps);
}
else
{
await next();
}
});
}
}
In .net core 6:
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
var options = new RewriteOptions()
.Add(RewriteRules.ReDirectRequestsToOneHost);
app.UseRewriter(options);
}
public class RewriteRules
{
public static void ReDirectRequestsToOneHost(RewriteContext context)
{
var request = context.HttpContext.Request;
if (request.Host != new HostString("www.example.com"))
{
var redirect = $"{request.Scheme}://www.example.com{request.Path}";
context.HttpContext.Response.Redirect(redirect);
}
}
}
You can use URLRewrite in .NET Core - just download ASP.NET Core 1.1 Preview 1. And then you will be able to do:
public class Startup {
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
var options = new RewriteOptions()
.AddRedirect("(.*)/$", "$1") // Redirect using a regular expression
.AddRewrite(#"app/(\d+)", "app?id=$1", skipRemainingRules: false) // Rewrite based on a Regular expression
.AddRedirectToHttps(302, 5001) // Redirect to a different port and use HTTPS
.AddIISUrlRewrite(env.ContentRootFileProvider, "UrlRewrite.xml") // Use IIS UrlRewriter rules to configure
.AddApacheModRewrite(env.ContentRootFileProvider, "Rewrite.txt"); // Use Apache mod_rewrite rules to configure
app.UseRewriter(options);
}
}
To update an existing project to ASP.NET Core 1.1 Preview 1 you will need to do the following:
Download and install the updated .NET Core 1.1 Prevew 1 SDK
Follow the instructions on the .NET Core 1.1 Preview 1 announcement to update your project to use .NET Core 1.1 Preview 1
Update your ASP.NET Core packages dependencies to use the new 1.1.0-preview1 versions
More info here: https://blogs.msdn.microsoft.com/webdev/2016/10/25/announcing-asp-net-core-1-1-preview-1/

Reload routes on runtime in dotnetcore

I have a custom route that reads URLs from a no-SQL database (MongoDB) and add them to the route pipeline at moment that the application starts, which is "pretty standard"
something like this (in my startup.cs file):
app.UseMvc(routes =>
{
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
routes.Routes.Add(
new LandingPageRouter(routes, webrequest, memoryCache, Configuration));
// this custom routes adds URLs from database
}
the issue is that if I add another route to the database after the application has started I basically get a 404 since the routing system isn't aware of this new route, I think that what I need is add the missing routes at runtime or (less convenient) restart the application pool from another web application (which has been developed on framework 4.5 and obviously it runs on a different pool)
Any other thoughts on this?
thanks.
The first question is what does database mean when you say: I add another route to the database and wether you can keep your routes in a JSON, XML or INI file.
If you can, for example, keep the routes in a JSON file, then there is possible for the routes to be dynamically available on runtime (as you can read in the ASP.NET Core Documentation)
You can find a full blog post about this here.
Assuming that routes.json is a JSON file with structure similar to the following, and is in the same folder as Startup:
{
"route": "This is a route",
"another-route": "This is another route",
"default": "This is default!"
}
You can configure the Startup class in the following way:
Note that this example does not use MVC but the separate routing package, although I assume you can transpose it to work with MVC.
public class Startup
{
public IConfiguration Configuration {get;set;}
public Startup(IHostingEnvironment env)
{
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("routes.json", optional: false, reloadOnChange: true);
Configuration = configurationBuilder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting();
}
public void Configure(IApplicationBuilder app)
{
var routeBuilder = new RouteBuilder(app);
routeBuilder.MapGet("{route}", context =>
{
var routeMessage = Configuration.AsEnumerable()
.FirstOrDefault(r => r.Key == context.GetRouteValue("route")
.ToString())
.Value;
var defaultMessage = Configuration.AsEnumerable()
.FirstOrDefault(r => r.Key == "default")
.Value;
var response = (routeMessage != null) ? routeMessage : defaultMessage;
return context.Response.WriteAsync(response);
});
app.UseRouter(routeBuilder.Build());
}
}
At this point, while the application is running, you can modify the JSON file, save it, then because of the reloadOnChange: true parameter, the framework will reinject the new configuration into the Configuration property.
The implementation of the reload is based on a file watcher, so if you want to use a database for this - a SQL Server, then I think you have to implement this yourself.
A (not pretty at all and reminded here just for completeness) solution could be to create a console application that adds database changes in a file, then use that file in the application configuration.
Best regards!
In ASP.NET Core 3 you can use Dynamic Routing. In Startup.cs add:
app.UseEndpoints(endpoints =>
{
endpoints.MapDynamicControllerRoute<SearchValueTransformer>("{**url}");
});
And create new class SearchValueTransformer
class SearchValueTransformer : DynamicRouteValueTransformer
{
public override async ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
var url = values["url"] as string;
if (String.IsNullOrEmpty(url))
return values;
values["controller"] = "Controller";
values["action"] = "Action";
values["name"] = url;
return values;
}
}
Also in method TransformAsync you can search in your MongoDB for proper Controller, Action and Name values. More info: https://weblogs.asp.net/ricardoperes/dynamic-routing-in-asp-net-core-3

Categories

Resources