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>();
}
}
Related
The problem when i send request to api
in my api my post methd and getAllUsers its work but
the GetUser method doesn't work its show me 404 error i was trying to solve it i used many route but still this problem
so if any one can help me to solve this problem this is my api code:
[EnableCors("MyPolicy")]
[Route("api/Login")]
[ApiController]
public class LoginController : ControllerBase
{
[EnableCors("MyPolicy")]
[HttpGet]
public IEnumerable<User> GetAllUsers()
{
IUserRepository obj = new UserRepository();
return obj.GetAll();
}
[Route("api/Login/GetUser/{id?}")]
[HttpGet]
public User GetUser(int id)
{
IUserRepository obj = new UserRepository();
return obj.Get(id);
}
[HttpPost]
public User PostAddUser(User item)
{
IUserRepository obj = new UserRepository();
return obj.Add(item);
}
and this is my service in angular :
Load(id:number){
debugger;
return this.httpcli.get("https://localhost:44366/api/Login/GetUser?id="+id);
}
and this my startup.cs in core api :
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options => options.AddPolicy(name: "MyPolicy", builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "LoginApi", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("MyPolicy");
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Change the action attribute routing:
[Route("~/api/Login/GetUser/{id?}")]
public User GetUser(int id)
{
IUserRepository obj = new UserRepository();
return obj.Get(id);
}
and url too:
this.httpclient.get("https://localhost:44366/api/Login/GetUser/"+id)
You can try to send a path parameter instead of a request parameter:
Load(id:number){
debugger;
return this.httpcli.get(`https://localhost:44366/api/Login/GetUser/${id}`);
}
I have Asp.net core(running on .net framework) web mvc application.
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var aiOptions = new Microsoft.ApplicationInsights.AspNetCore.Extensions.ApplicationInsightsServiceOptions
{
EnableQuickPulseMetricStream = true
};
services.AddMvc();
services.AddApplicationInsightsTelemetry(aiOptions);
services.AddCors(option =>
{
option.AddPolicy("AllowSpecificOrigin", policy => policy.WithOrigins("*"));
option.AddPolicy("AllowGetMethod", policy => policy.WithMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
TelemetryClient tc = new TelemetryClient();
tc.TrackTrace("Environment: " + env);
if (env.IsDevelopment())
{
tc.TrackTrace("Development");
app.UseDeveloperExceptionPage();
}
else
{
app.UseDeveloperExceptionPage();
}
app.Use((context, next) =>
{
context.Request.PathBase = new PathString("/Application1");
return next.Invoke();
});
app.UseStaticFiles();
app.UseCors(select => select.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
HomeController Action:
public class HomeController : Controller
{
public HomeController(StatelessServiceContext context){
}
public IActionResult UsernameAuthentication()
{
return View();
}
}
Action Executing:
http://localhost:9040/Home/UserNameAuthentication
Action not Executing:
http://localhost:9040/Application1/Home/UserNameAuthentication
Any other configuration i have to do,to execute Action with PathBase?or any other way to use context path to execute action.How?
Thanks.
As far as I know, if you use Application1/Home/UserNameAuthentication as the url pass to the application, the application context.Request.Path will be the Application1/Home/UserNameAuthentication then you add the pathbase, it will be Application1/Application1/Home/UserNameAuthentication this is the reason why you get 404 error.
To solve this issue, you should check whether the beginning of this Microsoft.AspNetCore.Http.PathString matches the specified Microsoft.AspNetCore.Http.PathString.
More details, you could refer to below codes:
app.Use((context, next) =>
{
string _pathBase = "/Application1";
PathString matchedPath;
PathString remainingPath;
if (context.Request.Path.StartsWithSegments(_pathBase, out matchedPath, out remainingPath))
{
var originalPath = context.Request.Path;
var originalPathBase = context.Request.PathBase;
context.Request.Path = remainingPath;
context.Request.PathBase = originalPathBase.Add(matchedPath);
var re = context.Request.PathBase;
return next.Invoke();
}
else
{
return next.Invoke();
}
});
Result:
I am using an Custom Action Filter attribute to check if session is null or not..
Authenticate.cs
public class Authenticate : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var data = context.HttpContext.Session.GetString("UserSession");
if (data == null)
{
bool isAjax = context.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
if (isAjax)
{
context.Result = new JsonResult("Session Expired!");
}
else
{
context.Result = new RedirectResult("/Account/Login");
}
}
}
public override void OnActionExecuted(ActionExecutedContext context)
{
// our code after action executes
}
}
I am calling this attribute on all controllers except Account Controller
AccountController.cs
public class AccountController : Controller
{
UserFunctions userFunctions;
public AccountController(ARSContext context)
{
userFunctions = new UserFunctions(context);
}
public IActionResult Index()
{
return View();
}
public IActionResult Login()
{
return View();
}
[HttpPost]
public IActionResult Login(AccountViewModel viewModel)
{
User user = userFunctions.Login(viewModel.Username, viewModel.Password);
if (user != null)
{
HttpContext.Session.SetString("UserSession", JsonConvert.SerializeObject(user));
return Json(true);
}
return Json(false);
}
}
My Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddDbContext<MyDBContext>(item => item.UseSqlServer(Configuration.GetConnectionString("MyDBEntities")));
services.AddScoped<CountryFunctions>();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromHours(1);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSession();
app.UseDeveloperExceptionPage();
if (env.IsDevelopment())
{
}
else
{
app.UseExceptionHandler("/Home/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();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Now the main problem is that all of this is working perfectly on my local computer when I run it through Visual Studio. But when I deploy it on IIS what happens is that when I call '/Account/Login' without 'HttpPOST' it shows you view.
But when I submit the form and call '/Account/Login' 'HttpPost' action then My Authenticate filter is called which redirects me back to '/Account/Login' View Action. I also tried to use ServiceFilters but same problem persists.
I've created a new controller in a brand new web api project in .net core 3.1. Whenever I try to post to the route I get a 404.
The controller is set as so:
[ApiController]
[Route("[controller]")]
public class AuthCodeController : Controller
{
private readonly ApplicationContext _context;
public AuthCodeController(ApplicationContext context)
{
_context = context;
}
[HttpPost]
[Consumes("application/x-www-form-urlencoded")]
public JsonResult GetAuthCode(AuthCode authCode)
{
try
{
var theCodes = _context.AuthCodes.ToList();
var tmpCode = new Random();
var myNum = tmpCode.Next(100000, 999999999);
while (theCodes.Any(tc => tc.AuthCodeRnd == myNum))
{
myNum = tmpCode.Next();
}
if (authCode.AuthCodeRnd > 0)
{
var removeCode = _context.AuthCodes.FirstOrDefault(c => c.AuthCodeRnd == authCode.AuthCodeRnd);
if (removeCode != null) _context.AuthCodes.Remove(removeCode);
}
Guid authGuid = Guid.NewGuid();
var tmpRec = new AuthCode
{
Guid = authGuid,
AuthCodeRnd = myNum,
Address = authCode.tAddress,
SmallLogoAddress = authCode.SmallLogoAddress,
ClientFolder = authCode.ClientFolder,
CompanyFolder = authCode.CompanyFolder,
Folder = authCode.Folder
};
_context.AuthCodes.Add(tmpRec);
_context.SaveChanges();
var retVals = new AuthResponse
{
Guid = authGuid,
ReturnAuthCode = myNum
};
return Json(retVals);
}
catch (Exception ex)
{
var message = new ErrorResponse();
message.Status = "An Error Has Occured";
message.Message = ex.ToString();
return Json(message);
}
}
}
When I POST to this method I receive a 404. I'm using the url https://localhost:44328/AuthCode/GetAuthCode
The only modifications I made in the startup.cs are adding the dbcontext service options. Everything else is default. I can get the weatherforecast to show.
EDIT - added startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
Solved: I needed to disable SSL Verification in POSTMan
You should set attribute: [HttpPost] → [HttpPost("GetAuthCode")] since your original route will be simple POST to 'https://localhost:44328/AuthCode'. Core Controller does use reflection to Your Controller name 'AuthCodeController' to form prefix for Your path ('/AuthCode' part). But it does not use reflection to form postfix from function name - You should form it yourself by parameter in HttpPost attribute.
I have a URL for like below:
https://localhost:44351/odata/PublicInsuranceOrder('To New1')
for it i have created below dotnet core controller which is working
[ODataRoutePrefix("PublicInsuranceOrder")]
public class PublicInsuranceOrderController : ODataController
{
[HttpGet]
[EnableQuery]
[SwaggerResponse("200", typeof(PublicInsuranceOrder))]
[SwaggerResponse("404", typeof(void))]
[ODataRoute("({insuranceOrderName})")]
public ActionResult<PublicInsuranceOrder> Get([FromODataUri] string insuranceOrderName)
{
//this works
}
Now I have a API with below route:
https://localhost:44351/odata/PublicInsuranceOrder('To New1') /Claim('1')
I have tried below controller method for it which is not working
[HttpGet]
[EnableQuery]
[SwaggerResponse("200", typeof(PublicInsuranceOrder))]
[SwaggerResponse("404", typeof(void))]
[ODataRoute("({insuranceOrderName})/Claim({id})")]
public ActionResult<PublicInsuranceOrder> Get([FromODataUri] string insuranceOrderName, string id)
{
//Not working
}
My startup.cs is like this:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddEnvironmentVariables();
builder.AddJsonFile(env.IsDevelopment() ? "appsettings.Development.json" : "appsettings.json", false, false);
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureServices(Configuration, typeof(WomBCModule), "Wom");
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); // _2);
services.AddTransient<PublicWorkOrder>();
services.AddOData();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Configure(env, true);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection();
// expand refs / which attr / query / count
//routeBuilder.Expand().Select().Filter().Count()
routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(100).Count();
//routeBuilder.MapODataServiceRoute("odata", "odata/v1", GetEdmModel());
//routeBuilder.MapODataServiceRoute("odata", "odata", GetEdmModel());
// Create the default collection of built-in conventions.
var conventions = ODataRoutingConventions.CreateDefault();
// Insert the custom convention at the start of the collection.
conventions.Insert(0, new NavigationIndexRoutingConvention());
routeBuilder.MapODataServiceRoute("odata", "odata", GetEdmModel(), new DefaultODataPathHandler(), conventions);
});
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<PublicWorkOrder>("PublicInsuranceOrder");
builder.EntitySet<Claim>("Claim");
return builder.GetEdmModel();
}
}
I have created a NavigationIndexRoutingConvention as shown below:
public partial class NavigationIndexRoutingConvention : IODataRoutingConvention
{
IEnumerable<ControllerActionDescriptor> IODataRoutingConvention.SelectAction(RouteContext routeContext)
{
// Get a IActionDescriptorCollectionProvider from the global service provider
IActionDescriptorCollectionProvider actionCollectionProvider =
routeContext.HttpContext.RequestServices.GetRequiredService<IActionDescriptorCollectionProvider>();
Contract.Assert(actionCollectionProvider != null);
// Get OData path from HttpContext
Microsoft.AspNet.OData.Routing.ODataPath odataPath = routeContext.HttpContext.ODataFeature().Path;
HttpRequest request = routeContext.HttpContext.Request;
// Handle this type of GET requests: /odata/Orders(1)/OrderRows(1)
if (request.Method == "GET" && odataPath.PathTemplate.Equals("~/entityset/key/navigation/key"))
{
// Find correct controller
string controllerName = odataPath.Segments[3].Identifier;
IEnumerable<ControllerActionDescriptor> actionDescriptors = actionCollectionProvider
.ActionDescriptors.Items.OfType<ControllerActionDescriptor>()
.Where(c => c.ControllerName == controllerName);
if (actionDescriptors != null)
{
// Find correct action
string actionName = "Get";
var matchingActions = actionDescriptors
.Where(c => String.Equals(c.ActionName, actionName, StringComparison.OrdinalIgnoreCase) && c.Parameters.Count == 2)
.ToList();
if (matchingActions.Count > 0)
{
// Set route data values
var keyValueSegment = odataPath.Segments[3] as KeySegment;
var keyValueSegmentKeys = keyValueSegment?.Keys?.FirstOrDefault();
routeContext.RouteData.Values[ODataRouteConstants.Key] = keyValueSegmentKeys?.Value;
var relatedKeyValueSegment = odataPath.Segments[1] as KeySegment;
var relatedKeyValueSegmentKeys = relatedKeyValueSegment?.Keys?.FirstOrDefault();
routeContext.RouteData.Values[ODataRouteConstants.RelatedKey] = relatedKeyValueSegmentKeys?.Value;
// Return correct action
return matchingActions;
}
}
}
// Not a match
return null;
}
}
I am getting below error:
404 NOT FOUND
Can i know what i am doing wrong