I can't make attribute routing to work in Web API 2. I did not start with Web API template project, I think it was Empty project. Convention based routing works, but when I add Route attribute to controller actions, I get 404 error. I find a lot of posts regarding this, but none of them helped me. It could be related to the project template, something I have to do manually since I started with Empty project. I do call MapHttpAttributeRoutes method in WebApiConfig and the class iheriting ApiController is public. What else do I have to do in WebApiConfig class and Application_Start method?
Thanks in advance.
Here is my WebApiConfig file:
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{oId}/{oTypeCode}/{oTypeName}/{email}",
defaults: new { controller = "Xrm", email = RouteParameter.Optional, oId = RouteParameter.Optional, oTypeCode = RouteParameter.Optional, oTypeName = RouteParameter.Optional}
);
}
And Application_Start method:
protected void Application_Start()
{
System.Web.Mvc.AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
log4net.Config.XmlConfigurator.Configure();
}
Of course, my fault. I was trying to pass an email address as a path parameter. Sending it as query string parameter works. I am not sure how would it look like to send as part of the path, but it works for me this way.
Related
We have a .NET Core Web API deployed as an Azure Web App. All endpoint work locally, however, once deployed, we have one controller that is gives us a 404 for all endpoint we hit within it.
We have checked and triple checked that the url we are calling is correct & from what we can tell, there is nothing different about this controller relative to the others in our application.
This is our BinController that is giving us 404's:
namespace API.Controllers
{
[Route("api/[controller]")]
[Authorize]
[ApiController]
public class BinController : ControllerBase
{
private readonly IBinRepository _binRepo;
private readonly ILogger _logger;
public BinController(IBinRepository binRepo, ILogger<BinController> logger)
{
_binRepo = binRepo;
_logger = logger;
}
[HttpGet("{locationId}/{binId}")]
public async Task<IActionResult> CheckBinExists(int locationId, string binId)
{
try
{
bool result = await _binRepo.CheckBinExists(locationId, binId);
return Ok(result);
}
catch (Exception e)
{
return StatusCode(StatusCodes.Status500InternalServerError, e.Message);
}
}
[HttpGet("findAll/{locationId}/{itemId}")]
public async Task<IActionResult> FindAllBinsWithItem(int locationId, string itemId)
{
try
{
var result = await _binRepo.FindAllBinsWithItem(locationId, itemId);
return Ok(result);
}
catch (Exception e)
{
_logger.LogError(e.Message);
return StatusCode(StatusCodes.Status500InternalServerError, e.Message);
}
}
[HttpGet("contents/{locationId}/{bin}")]
public async Task<IActionResult> GetBinContents(int locationId, string bin)
{
try
{
List<BatchLine> contents = await _binRepo.GetBinContents(locationId, bin);
return Ok(contents);
}
catch (Exception e)
{
_logger.LogError(e.Message);
return StatusCode(StatusCodes.Status500InternalServerError, e.Message);
}
}
}
}
We are calling https://ourapiname.azurewebsites.net/api/Bin/1234/TestBin.
To Summarize:
All endpoints work locally
All controllers work when deployed except for one
We have multiple other controllers in our application with similar if not the same setup, but this one is returning a 404 when deployed
We saw these similar posts, but they did not resolve this issue:
Web API interface works locally but gets 404 after deployed to Azure Website
Web api call works locally but not on Azure
I wish I could provide more insight, but we are really at a loss for what could be going on here. Any ideas would be greatly appreciated.
You can deploy your web project with Self-Contained mode, then find the project_name.exe and double-click it. Test it in your local, and your test url should be https://localhost:5001/api/Bin/1234/TestBin.
If you can run it works well in your local, the second step you should to create a new webapp, then deploy it as usual. We just rult out the some specifical reason in your original webapp.(like: deploy failed)
If it still not work, my suggestion is you can manually drag and drop the publish file to the kudu site of the azure webapp.
The steps above should be useful to you, but I think the easiest way is to go to the kudu site to get the dll file, and then decompile it, so that the root cause of the problem can be found.
This does not make any sense, but I simply changed the name of the Controller from "BinController" to "BinsController" and now it works...
Must not be able to name a controller "Bin".
Is it possible to tell Application Insights to use a different InstrumentationKey depending on the request URL?
Our application works with different clients and we want to separate the logs for them in different instances of Application Insights.
Url format: https://webapi.com/v1/{client_name}/bla/bla
It would be great to setup configuration to select InstrumentationKey by client_name from request.
If the goal is to send different telemetry items to different instrumentation key, the correct way to achieve that is by modifying the individual item with a TelemetryInitializer to have the correct ikey.
An initializer like the following:
item.Context.InstrumentationKey = ikey.
This initializer should access HttpContext and decide the ikey dynamically from request route/other params.
Modifying TC.Active is not recommended for this purpose as its a global shared setting.
(This is not a very common use case - but there are teams inside Microsoft who does this for PROD scale apps)
You can do that. If you have a logger, have the ApplicationInsightsKey parameter-ized and pass the Key for the client on every call, or inject it on load if your application is tenant based.
Checkout the Docs here: Separating telemetry from Development, Test, and Production
Microsoft.ApplicationInsights.Extensibility.
TelemetryConfiguration.Active.InstrumentationKey = <App-Insights-Key-for-the-client>
Just change the Application Insights key before logging and it will do the job.
It would be great to setup configuration to select InstrumentationKey
by client_name from request.
You can dynamically select the ikey as per the client_name from the request. First, you need to get the request url, then check the client_name.
To do that, you can add the following code to the Global.asax file:
void Application_BeginRequest(Object source, EventArgs e)
{
var app = (HttpApplication)source;
//get the request url
var uriObject = app.Context.Request.Url.ToString();
if (uriObject.Contains("/client_name_1"))
{
Microsoft.ApplicationInsights.Extensibility.
TelemetryConfiguration.Active.InstrumentationKey = "ikey_1";
}
else if (uriObject.Contains("/client_name_2"))
{
Microsoft.ApplicationInsights.Extensibility.
TelemetryConfiguration.Active.InstrumentationKey = "ikey_2";
}
else
{
Microsoft.ApplicationInsights.Extensibility.
TelemetryConfiguration.Active.InstrumentationKey = "ikey_3";
}
}
The test result:
But I want to say we rarely use 1 more ikeys in one environment. If your goal is to make the data not being cluttered, I suggest you can use only 1 ikey, and then use Kusto query for your purpose.
Thanks to the answers from #cijothomas and #danpop (link) I was able to understand the whole picture.
Step 1: Create custom ITelemetryInitializer (Microsoft Documentation):
public class MyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var appKey = CallContext.LogicalGetData("ApplicationKey")?.ToString();
switch (appKey)
{
case "App1":
telemetry.Context.InstrumentationKey = "d223527b-f34e-4c47-8aa8-1f21eb0fc349";
return;
default:
telemetry.Context.InstrumentationKey = "f8ceb6cf-4357-4776-a2b6-5bbed8d2561c";
return;
}
}
}
Step 2: Register custom initializer:
<ApplicationInsights xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings">
<TelemetryInitializers>
<Add Type="Application.WebAPI.MyTelemetryInitializer, Application.WebAPI"/>
</TelemetryInitializers>
<!--<InstrumentationKey>f8ceb6cf-4357-4776-a2b6-5bbed8d2561c</InstrumentationKey>-->
</ApplicationInsights>
OR
protected void Application_Start()
{
// ...
TelemetryConfiguration.Active.TelemetryInitializers.Add(new MyTelemetryInitializer());
}
Step 3: Make some adjustments to the logger (source code taken from #danpop answer Logger target configuration):
var config = new LoggingConfiguration();
ConfigurationItemFactory.Default.Targets.RegisterDefinition("ai", typeof());
ApplicationInsightsTarget aiTarget = new ApplicationInsightsTarget();
aiTarget.InstrumentationKey = "your_key";
aiTarget.Name = "ai";
config.AddTarget("ai", aiTarget);
LogManager.Configuration = config;
ILogger configuration exmples: Log4Net, NLog, System.Diagnostics
In my Azure Mobile .NET backend I want to use Azure Mobile .NET Server Swagger . I'm looking for fast way to hide swagger UI from public access ? Is there any way to provide access only for selected users ?
First a disclaimer: Even if you protect your Swagger UI from public consumption, you are not protecting your APIs from public consumption. You have to assume that everyone knows all of your routes and have the appropriate security in place to protect any requests that may come in.
That being said, there's still not a simple way to do this. Swashbuckle (the piece that adds Swagger to Web API) adds a custom HttpMessageHandler to the /swagger/ui route (as seen here). If you look at the Web API pipeline, you can see that if you specify a custom handler, you can bypass all of the Controller selection, Auth filters, etc. This is what happens here.
Some solutions:
Use an app setting to conditionally call ConfigureSwagger(config) in debug modes only. This would prevent all /swagger routes from making it into production. Or you could use a staging slot and only add it there.
You can wrap the SwaggerUiHandler with something like this Basic Auth MessageHandler. This would prompt the user for basic creds if they went to the /swagger/ui route. See below for my modified version of this code.
Maybe with a little more thought we can come up with a better solution -- I see a couple of issues (here and here) in the Swashbuckle repo that indicate you're not the first one to hit this.
Modified BasicAuthHandler (from here):
Warning: minimally tested (and be sure to change how you verify user/pass)
public class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic";
public BasicAuthMessageHandler(HttpMessageHandler innerHandler)
{
this.InnerHandler = innerHandler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
AuthenticationHeaderValue authValue = request.Headers.Authorization;
HttpResponseMessage unauthorizedResponse = request.CreateUnauthorizedResponse();
if (authValue != null && !string.IsNullOrWhiteSpace(authValue.Parameter))
{
Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
if (parsedCredentials != null)
{
// TODO: Check that the user/pass are valid
if (parsedCredentials.Username == "user" &&
parsedCredentials.Password == "pass")
{
// If match, pass along to the inner handler
return base.SendAsync(request, cancellationToken);
}
}
}
else
{
// Prompt for creds
unauthorizedResponse.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
}
return Task.FromResult(unauthorizedResponse);
}
private Credentials ParseAuthorizationHeader(string authHeader)
{
string[] credentials = Encoding.ASCII.GetString(Convert
.FromBase64String(authHeader))
.Split(
new[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0])
|| string.IsNullOrEmpty(credentials[1])) return null;
return new Credentials()
{
Username = credentials[0],
Password = credentials[1],
};
}
}
Registering with Swagger route
// Do this after calling ConfigureSwagger
ConfigureSwagger(config);
// Remove the swagger_ui route and re-add it with the wrapped handler.
var route = config.Routes["swagger_ui"];
config.Routes.Remove("swagger_ui");
config.Routes.MapHttpRoute("swagger_ui", route.RouteTemplate, route.Defaults, route.Constraints, new BasicAuthMessageHandler(route.Handler));
I'm trying to use HttpRequestHandlerServlet in common with HttpRequestHandlingMessagingGateway to expose simple REST URL to browser. But I cannot register HttpRequestHandlerServlet, I'm doing it in the following way:
#Bean
public ServletRegistrationBean inboundServletRegistration(ApplicationContext context) {
final HttpRequestHandlerServlet servlet = new HttpRequestHandlerServlet();
ServletRegistrationBean registration = new ServletRegistrationBean(
servlet, "/demo/*");
registration.setName("inboundServletRegistration");
return registration;
}
Spring boot application start's ok, but when try to access HttpRequestHandlingMessagingGateway endpoint with mapping:
#Bean
public HttpRequestHandler httpInboundEndPoint() {
// Http Rest gateway expecting reply.
final HttpRequestHandlingMessagingGateway restGateway = new
HttpRequestHandlingMessagingGateway(true);
// Mapping of URL this gateway consumes...
restGateway.setRequestMapping(
mapping(new HttpMethod[]{HttpMethod.GET}, "/context/{param}"));
at address http://localhost:8080/demo/context/{param} I get total nonsense crash:
org.springframework.beans.factory.BeanNotOfRequiredTypeException:
Bean named 'inboundServletRegistration' must be of type [org.springframework.web.HttpRequestHandler], but was actually of type [org.springframework.boot.context.embedded.ServletRegistrationBean]
Did you come across to this problem? Can you please help me out?
Okay, I figured it out.
Key to pass this problem is to register HttpRequestHandler under the same bean name as the HttpRequestHandlerServlet registration bean...Going rather back to XML config...:-(
I'm developing a website using ASP.NET MVC5 and WEB API 2.2. In my web api controller, I have an if condition to check if user is logged in:
if (!User.Identity.IsAuthenticated)
{
return BadRequest("NotLoggedIn");
}
This works fine for if there's no "www" in front of my domain name. But it will return BadRequest("NotLoggedIn") if "www" Is added to my domain name. For example, my domain is example.com. If user type www.example.com, the webpage will make an AJAX request to example.com/api/controller, and User.Identity.IsAuthenticated will return false. It will return ture if user just enter example.com.
I have enabled CORS in the global level:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var cors = new EnableCorsAttribute("http://www.example.com", "*", "*");
config.EnableCors(cors);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Anyone knows how to solve this problem?
I have solved this problem.
I need to decorate the api controller with:
[EnableCorsAttribute("http://www.example.com" , "*", "*", SupportsCredentials = true)]