We have several modules in our software that ship as a single product. When a module is activated, those features become available. We would like our OData APIs to follow the same pattern. However I can't figure out how to make the $metadata ignore controllers for modules that have been disabled. Basically I want to determine what is available at any time instead of application start time.
We are using the following type of cod to register the routes:
static public void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Module1Entity>("Module1Entities");
builder.EntitySet<Module2Entity>("Module2Entities");
config.MapODataServiceRoute("odata", "api", builder.GetEdmModel());
}
protected void Application_Start(object sender, EventArgs e)
{
GlobalConfiguration.Configure(Register);
}
So we only want Module1Entity to show up in the metadata if the module has been activated. We already have code to disable the associated controller when the module is deactivated.
Any ideas?
I ended up finding a solution:
static public void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
var builder = new ODataConventionModelBuilder();
if (IsModule1Enabled)
{
builder.EntitySet<Module1Entity>("Module1Entities");
{
if (IsModule2Enabled)
{
builder.EntitySet<Module2Entity>("Module2Entities");
}
var conventions = ODataRoutingConventions.CreateDefault();
conventions.Insert(0, new MyAttributeRoutingConvention("odata", config));
config.MapODataServiceRoute("odata", "api", builder.GetEdmModel(), new DefaultODataPathHandler(), conventions);
}
public class MyAttributeRoutingConvention : AttributeRoutingConvention
{
public MyAttributeRoutingConvention(string routeName, HttpConfiguration configuration) : base(routeName, configuration)
{
}
public override bool ShouldMapController(HttpControllerDescriptor controller)
{
if (controller.ControllerType == typeof(Module1EntitiesController))
{
return IsModule1Enabled;
}
if (controller.ControllerType == typeof(Module2EntitiesController))
{
return IsModule2Enabled;
}
return base.ShouldMapController(controller);
}
}
protected void Application_Start(object sender, EventArgs e)
{
GlobalConfiguration.Configure(Register);
}
Finally i found easier way.
This startup code ovveride OData $metadata path and return response forbidden.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.EnableDependencyInjection();
endpoints.Select().Filter().OrderBy().Count().MaxTop(24);
endpoints.MapODataRoute("ODataRoute", "odata", GetEdmModel());
endpoints.MapGet("/odata/$metadata", async context =>
{
context.Response.StatusCode = 403;
await context.Response.WriteAsync("Forbidden!");
});
endpoints.MapGet("/", context =>
{
context.Response.Redirect("/swagger");
return Task.CompletedTask;
});
});
Related
I was working with KOA 2.0 and started to test asp.net core. But can't find a way to handle request/url specific middleware
Say in Koa, with router I can achieve the below:
.post("/api/v1/user", Middleware1, Middleware2, RequestValidationMiddleware, SpecificAction);
.get("/api/v1/user", Middleware1, Middleware2, RequestValidationMiddleware, SpecificAction1);
.post("/api/v1/role", Middleware1, Middleware4, RequestValidationMiddleware2, SpecificAction2);
How to achieve it with asp.net core?
Tried the below:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//app.UseApiLog();
app.Map("/api", ApiLogApps);
app.Map("/exlog", ExceptionLogApps);
//app.UseMvc(routes =>
//{
// routes.MapRoute(
// name: "default",
// template: "apilog/{controller}/{action}");
// routes.MapRoute(
// name: "default2",
// template: "exlog/{controller=Home}/{action=Index}/{id:int}");
//});
}
private static void ApiLogApps(IApplicationBuilder app)
{
//app.Run(() => )
app.UseApiLog();
app.UseMvc();
}
And in controller I have
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("test/get/{id}")]
public string Get(int id)
{
return "value";
}
}
But I am lost here.
What I want is, I want to have DataValidation to be handled in a middleware - that forces me to have per url (almost) specific middleware.
PS - I know, model validation can be done in action, but I don't want that.
Thanks in advance for your help :)
To use middlewares like Koa2 , you can configure a sequence of middlewares to build a route :
public IRouter BuildRouter(IApplicationBuilder applicationBuilder)
{
var builder = new RouteBuilder(applicationBuilder);
// use middlewares to configure a route
builder.MapMiddlewareGet("/api/v1/user", appBuilder => {
// your Middleware1
appBuilder.Use(Middleware1);
appBuilder.Use(Middleware2);
appBuilder.Use(RequestValidationMiddleware);
appBuilder.Run(SpecificAction);
});
builder.MapMiddlewarePost("/api/v1/user", appBuilder => {
// your Middleware1
appBuilder.Use(Middleware1);
appBuilder.Use(Middleware2);
appBuilder.Use(RequestValidationMiddleware);
appBuilder.Run(SpecificAction1);
});
// ....
return builder.Build();
}
and then use RouterMiddleware via UseRouter(router) :
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
app.UseRouter(BuildRouter(app));
// ...
app.UseMvc();
}
a screenshot:
[Update]:
To integrate with attribute routing, just add a UseMvc() insteand of Run() as below :
public IRouter BuildRouter(IApplicationBuilder applicationBuilder)
{
var builder = new RouteBuilder(applicationBuilder);
// use middlewares to configure a route
builder.MapMiddlewareGet("/api/v1/user", appBuilder => {
appBuilder.Use(Middleware1);
appBuilder.Use(Middleware2);
appBuilder.Use(RequestValidationMiddleware);
appBuilder.UseMvc(); // use a MVC here ...
});
builder.MapMiddlewarePost("/api/v1/user", appBuilder => {
appBuilder.Use(Middleware1);
appBuilder.Use(Middleware2);
appBuilder.Use(RequestValidationMiddleware);
appBuilder.UseMvc();
});
// ....
return builder.Build();
}
Just for a demo , the Middleware1 is a dummy middleware that adds a HttpContext.Items['mw-message1']:
private Func<RequestDelegate, RequestDelegate> Middleware1 = next=> {
return async context =>
{
context.Items["mw-message1"] = "mw1";
await next(context);
};
};
the Controller is a plain controller that will retrieve the HttpContext.Items["mw-messages1"]:
[Route("api/v1/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
public IActionResult Index()
{
var x = (string)HttpContext.Items["mw-message1"];
return new JsonResult(new {
MW1 = x,
Hello= "Hello",
});
}
}
and now , when make a Get request to /api/v1/user , the final response is :
{"mW1":"mw1","hello":"Hello"}
I am using below code in for .net core 3 webapi.
add below in startup.cs configure method to add middleware for specific route
//Middleware to check Authorization key is present in the request header
//Map to all routes
_ = app.UseHeaderKeyAuthorization();
//Map to all routes
_ = app.MapWhen(context => context.Request.Path.StartsWithSegments("/api/v1.0"), appBuilder =>
{
_ = appBuilder.UseHeaderKeyAuthorization();
});
create middleware HeaderKeyAuthorizationMiddleware, sample below
//HeaderKeyAuthorizationMiddleware
public class HeaderKeyAuthorizationMiddleware
{
private readonly RequestDelegate next;
public HeaderKeyAuthorizationMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var authHeader = httpContext.Request.Headers[ApiConstants.AuthHeaderName];
//check authHeader logic
if (!string.IsNullOrEmpty(authHeader))
{
await next.Invoke(httpContext);
return;
}
//Reject request if there is no authorization header or if it is not valid
httpContext.Response.StatusCode = 401;
await httpContext.Response.WriteAsync("Unauthorized");
}
}
//Middleware extension to register middleware
public static class HeaderKeyAuthorizationMiddlewareExtension
{
public static IApplicationBuilder UseHeaderKeyAuthorization(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware<HeaderKeyAuthorizationMiddleware>();
}
}
this thing takes one week from me
i have done many methods to find a solution
mvc fully integrated with autofac, but web api NO and NO! :-(
here is my codes:
AutofacDi
public static class AutofacDi
{
public static ValueTuple<IContainer, HttpConfiguration> Initialize()
{
var assembly = Assembly.GetExecutingAssembly();
var builder = new ContainerBuilder();
var config = GlobalConfiguration.Configuration;
builder.RegisterControllers(assembly);
builder.RegisterApiControllers(assembly).PropertiesAutowired();
builder.RegisterHttpRequestMessage(config);
builder.RegisterAssemblyModules(assembly);
builder.RegisterAssemblyTypes(assembly).PropertiesAutowired();
builder.RegisterFilterProvider();
builder.RegisterWebApiFilterProvider(config);
builder.RegisterModelBinders(assembly);
builder.RegisterWebApiModelBinderProvider();
builder.RegisterModelBinderProvider();
builder.RegisterModule<AutofacWebTypesModule>();
builder.RegisterSource(new ViewRegistrationSource());
builder.RegisterType<T4MVC.Dummy>().AsSelf();
builder.RegisterType<FoodDbContext>()
.As<IUnitOfWork>()
.InstancePerLifetimeScope();
builder.Register(context => (FoodDbContext)context.Resolve<IUnitOfWork>())
.As<FoodDbContext>()
.InstancePerLifetimeScope();
builder.RegisterType<ApplicationDbContext>().As<DbContext>().InstancePerLifetimeScope();
builder.RegisterType<UserStore<ApplicationUser>>().As<IUserStore<ApplicationUser>>();
builder.RegisterType<ApplicationUserManager>();
builder.RegisterType<ApplicationSignInManager>();
builder.Register(c => new IdentityFactoryOptions<ApplicationUserManager>()
{
DataProtectionProvider = new DpapiDataProtectionProvider("FoodBaMa")
});
builder.Register(c => HttpContext.Current.GetOwinContext().Authentication).InstancePerLifetimeScope();
builder.RegisterType<RoleStore<IdentityRole>>().As<IRoleStore<IdentityRole, string>>();
builder.RegisterAssemblyTypes(typeof(MvcApplication).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
return new ValueTuple<IContainer, HttpConfiguration>(container, config);
}
}
OWIN Startup
[assembly: OwinStartup(typeof(FoodBaMa.Startup))]
namespace FoodBaMa
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
var iOc = AutofacDi.Initialize();
app.UseAutofacMiddleware(iOc.Item1);
app.UseAutofacMvc();
app.UseWebApi(iOc.Item2);
app.UseAutofacWebApi(iOc.Item2);
WebApiConfig.Register(iOc.Item2);
app.UseCors(CorsOptions.AllowAll);
ConfigureAuth(app);
}
}
}
Global
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());
ModelBinders.Binders.Add(typeof(string), new PersianModelBinder());
MvcHandler.DisableMvcResponseHeader = true;
DbInterception.Add(new ElmahEfInterceptor());
DbInterception.Add(new YeKeInterceptor());
GlobalConfiguration.Configuration.EnsureInitialized();
}
}
ApiController
[AutofacControllerConfiguration]
[WebApiCompress]
[RoutePrefix("api/v1")]
public class AppController : ApiController
{
private readonly IApiV1Service _apiService;
public AppController(
IApiV1Service apiService
)
{
_apiService = apiService;
}
[HttpPost]
[Route("app/mainview")]
public virtual async Task<IHttpActionResult> MainView([FromBody] Request model)
{
var Result = new Models.API.V1.App.MainView.Response { Status = CheckTokenEnum.Error };
try
{
if (ModelState.IsValid)
Result = await _apiService.MainView(model).ConfigureAwait(false);
}
catch (Exception ex)
{
ErrorLog.GetDefault(null).Log(new Error(ex));
}
return Json(Result, R8.Json.Setting);
}
}
WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.JsonIntegration();
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
config.MessageHandlers.Add(new CachingHandler(new InMemoryCacheStore()));
config.MessageHandlers.Add(new PreflightRequestsHandler());
config.Filters.Add(new ElmahHandleErrorApiAttribute());
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
RouteConfig
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//routes.IgnoreRoute("{*browserlink}", new { browserlink = #".*/arterySignalR/ping" });
routes.MapMvcAttributeRoutes();
AreaRegistration.RegisterAllAreas();
//routes.LowercaseUrls = true;
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
},
namespaces: new[] { "FoodBaMa.Controllers" }
);
}
}
on each web api request, returns:
HTTP Error 404.0 - Not Found
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.
Module: IIS Web Core
Notification: MapRequestHandler
Handler: StaticFile
Error Code: 0x80070002
It's a killing problem for me because it's two weeks that making my website application unusable.
i don't know what to do.
help me !!!
i fixed that issue by commenting:
builder.RegisterWebApiFilterProvider(config);
and
builder.RegisterHttpRequestMessage(config);
in AutofacDi
It appears you may have gotten past your issue, which I hope is true. There's a lot to digest in here, but I do see a common mistake in the very start with respect to OWIN integration and Web API as documented in the Autofac docs:
A common error in OWIN integration is use of the GlobalConfiguration.Configuration. In OWIN you create the configuration from scratch. You should not reference GlobalConfiguration.Configuration anywhere when using the OWIN integration.
You may run into additional/other challenges in your setup as it sits; if you do, try getting rid of the use of GlobalConfiguration.Configuration.
This is my Startup.cs where I map my index page to the route '/app'.
...
using Microsoft.Owin.FileSystems;
using Microsoft.Owin.StaticFiles;
using Microsoft.Owin.Diagnostics;
[assembly: OwinStartup(typeof(conApi.Startup))]
namespace conApi
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
////Set static files
ConfigureFiles(app);
//Enable Cors
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
}
public void ConfigureFiles(IAppBuilder app)
{
app.Map("/app", spa =>
{
spa.Use((context, next) =>
{
context.Request.Path = new PathString("/index.html");
return next();
});
spa.UseStaticFiles();
});
}
}
}
It works like a charm but I don't know how to configure the client caching. I would like to know how to set the Expires header if that is possible when using OWIN static files?
SOLUTION
Tratcher provided the link to the StaticFilesOptions class documentation etc which lead me to a solution. Added the StaticFilesOptions to the ConfigureFiles method like this:
public void ConfigureFiles(IAppBuilder app)
{
var staticFileOptions = new StaticFileOptions
{
OnPrepareResponse = (StaticFileResponseContext) =>
{
StaticFileResponseContext.OwinContext.Response.Headers.Add("Cache-Control",new[] { "public", "max-age=1000" });
}
};
app.Map("/app", spa =>
{
spa.Use((context, next) =>
{
context.Request.Path = new PathString("/index.html");
return next();
});
spa.UseStaticFiles(staticFileOptions);
});
}
You can pass a StaticFilesOptions to UseStaticFiles. On the options use the OnPrepareResponse event to customize your responses. See http://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.StaticFiles/StaticFileOptions.cs
I am new to OData. I have built an ASP.NET Web API controller as shown below:
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Web.OData.Routing;
namespace HelloWebApi.Controllers
{
public class TestsController : ODataController
{
ProductsContext db = new ProductsContext();
private bool TestExists(int key)
{
return db.tests.Any(p => p.key== key);
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
[EnableQuery]
public IQueryable<test> Get()
{
return db.tests;
}
}
}
The model is as shown below:
public class Test
{
[Key]
public int key { get; set; }
public string aaa { get; set; }
}
I have also configured the RouteConfig, ODdataConfig, and WebApiConfig as shown below:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.Ignore("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "Default",
routeTemplate: "{controller}/{action}/{id}"
);
}
}
public class ODataConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Test>("Tests");
config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
As well as the global.asax file:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(config =>
{
ODataConfig.Register(config); //this has to be before WebApi
WebApiConfig.Register(config);
});
//FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
I tried making a number of modifications in order to resolve this. But I am consistently getting an HTTP 404 Not Found response. I also tried explicitly adding an [ODataRoute] attribute to the action method name; when doing that I instead get an HTTP 406 Not Acceptable response.
The URL I am trying to configure is:
http://localhost:6701/odata/tests/
Where odata is the suffix and tests is the controller name. Please point out what I am doing wrong.
The routes configured by the ODataConventionModelBuilder are case-sensitive. In your code, you have defined:
builder.EntitySet<Test>("Tests");
Based on this, the endpoint will be:
http://localhost:6701/odata/Tests/
Note the upper-case T in Tests.
This is by design, in order to maintain compatibility with the OData specification.
That said, as of Web API OData 5.4, you can optionally enable case-insensitive routes using the HttpConfiguration class's EnableCaseInsensitive() method. E.g., in your ODataConfig.Register() method you could add:
config.EnableCaseInsensitive(caseInsensitive: true);
For more information, see Basic Case Insensitive Support under Microsoft's ASP.NET Web API for OData V4 Docs.
I search github source and http://docs.asp.net/en/latest, but can't find any documentation for RouteExistingFiles. I've tried adding it onto routes.RouteExistingFiles, but this won't compile. Has this option be removed or rethought? Can it be accessed from the StartUp?
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddRouting();
}
public void Configure(IApplicationBuilder app)
{
StaticFileOptions option = new StaticFileOptions();
FileExtensionContentTypeProvider contentTypeProvider = (FileExtensionContentTypeProvider) option.ContentTypeProvider;
contentTypeProvider.Mappings.Add(".yqs", "text/plain");
app
.UseStaticFiles(option)
.UseDefaultFiles()
.UseFileServer()
.UseMvc(routes =>
{
routes.MapRoute(
"YQ Controller",
"{*src}",
new { controller = "YQFile", action = "OnDemand" },
new { src = #"(.*?)\.(yqs)" }
);
});
}
}
Though I can't find any documentation, it seems this option isn't in asp.net 5. Now, it seems that routing and other configuration in StartUp.Configure() will take precedence over proceeding configuration. So, bringing the app.UseMvc() configuration forward in the chain will allow the route to take priority over app.UseStaticFiles().
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddRouting();
}
public void Configure(IApplicationBuilder app)
{
app
.UseMvc(routes =>
{
routes.MapRoute(
"YQ Controller",
"{*src}",
new { controller = "YQFile", action = "OnDemand" },
new { src = #"(.*?)\.(yqs)" }
);
})
.UseStaticFiles()
.UseDefaultFiles()
.UseFileServer();
}
}