I am trying to implement an Options method in a controller of my web service that will return a message containing the valid HTTP methods for the URI endpoint associated with the controller. My Options method looks something like this:
public HttpResponseMessage Options()
{
var resp = new HttpResponseMessage();
resp.Content = new StringContent("");
var apiExplorer = GlobalConfiguration.Configuration.Services
.GetApiExplorer();
foreach (ApiDescription api in apiExplorer.ApiDescriptions)
{
resp.Content.Headers.Add("Allow", api.HttpMethod.Method);
}
return resp;
}
I have tried the above method in a brand-new Web Api project (implication: unaltered routing) inside of a controller with Get, Post, and Delete methods. As expected, a response with "Allow: GET, POST, DELETE" is returned. I am having trouble, however, adding this to a larger project that I am working on. In the larger project, the ApiDescriptions list within ApiExplorer does not contain any elements. Why is this? I suspect it is due to the custom routing that has been implemented, although the only basis for that suspicion is the following link:
http://forums.asp.net/t/1821651.aspx/1
Has anybody else experienced this empty ApiDescription list? If so, did you find a remedy?
Note: I am using MCV 4 RC
If you use Glimpse, you might have to disable it's route inspector:
<glimpse defaultRuntimePolicy="On" endpointBaseUri="~/Glimpse.axd">
<logging level="Off" />
<tabs>
<ignoredTypes>
<add type="Glimpse.AspNet.Tab.Routes, Glimpse.AspNet" />
</ignoredTypes>
</tabs>
<inspectors>
<ignoredTypes>
<add type="Glimpse.AspNet.Inspector.RoutesInspector, Glimpse.AspNet" />
</ignoredTypes>
</inspectors>
</glimpse>
Glimpse creates RouteProxies that break enumeration in HostedHttpRouteCollection:
https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/Routing/HostedHttpRouteCollection.cs
I know the link is for mono but the same is true for standard .Net.
You should look to upgrade to the RTM of WebApi that was released yesterday and then check out the newly released ASP.NET WebApi Help Page (Preview) that was also released yesterday.
This package automatically generates help page content for Web APIs on your site. Visitors to your help page can use this content to learn how to call your web APIs. Everything generated by the help page is fully customizable using ASP.NET MVC and Razor.
It is implementing the ApiExplorer under the covers.
The solution for this problem is to comment in ProjectName\Areas\HelpPage\Controllers\HelpController.cs the constructors like this:
public class HelpController : Controller
{
private const string ErrorViewName = "Error";
// public HelpController()
// : this(GlobalConfiguration.Configuration)
// {
// }
// public HelpController(HttpConfiguration config)
// {
// Configuration = config;
// }
/// <summary>
/// GlobalConfiguration By default
/// </summary>
protected static HttpConfiguration Configuration
{
get { return GlobalConfiguration.Configuration; }
}
public ActionResult Index()
{
ViewBag.DocumentationProvider = Configuration.Services.GetDocumentationProvider();
return View(Configuration.Services.GetApiExplorer().ApiDescriptions);
}
....
The default constructor is not called;
The second method is to inject the default constructor by add this attribute [InjectionConstructor] on default constructor like this:
public class HelpController : Controller
{
private const string ErrorViewName = "Error";
[InjectionConstructor]
public HelpController()
: this(GlobalConfiguration.Configuration)
{
}
public HelpController(HttpConfiguration config)
{
Configuration = config;
}
/// <summary>
/// GlobalConfiguration By default
/// </summary>
protected static HttpConfiguration Configuration { get; private set; }
....
Related
i use asp.net boilerplate for my project. i updated devexpress version from 21.1.4 to 21.2.5 and made a custom WebDocumentViewerController.
public class CustomWebDocumentController :
WebDocumentViewerController
{
public
CustomWebDocumentController(IWebDocumentViewerMvcControllerService
controllerService) : base(controllerService)
{
}
}
i used this code to remove defualt DocumentViewerController in startup.sc:
services.AddMvc()
.ConfigureApplicationPartManager(x =>
{
var parts = x.ApplicationParts;
var aspNetCoreReportingAssemblyName =
typeof(WebDocumentViewerController).Assembly.GetName().Name;
var reportingPart = parts.FirstOrDefault(part => part.Name
== aspNetCoreReportingAssemblyName);
if (reportingPart != null)
{
parts.Remove(reportingPart);
}
});
the code is running but the defualtcontroller is still in list of controllers and makes swagger confiused.
how should i remove the defualt contoller?
thanks for your time.
The reason why this "ambiguous HTTP method for Action Error" pops up is because this controller 'CustomWebDocumentController' is missing the HTTP action decoration ([HttpGet],[HttpPost] etc) on top of it.
Simply decorate the controller with '[ApiExplorerSettings(IgnoreApi=true)]'. This will ultimately cause the entire controller or individual action to be omitted from the Swagger output.
Source: Exclude controllers methods from docs without using the Obsolete attribute
public class CustomWebDocumentController : WebDocumentViewerController
{
[ApiExplorerSettings(IgnoreApi = true)]
public
CustomWebDocumentController(IWebDocumentViewerMvcControllerService controllerService) : base(controllerService)
{
}
}
What I have done is created a small API in a class library. This API would be used by other sites. Think of it as a standard endpoint that all of our websites will contain.
[Route("api/[controller]")]
[ApiController]
public class CustomController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
The above is in a class library. Now what i would like to do is be able to add this to the projects in a simple manner.
app.UseCustomAPI("/api/crap");
I am not exactly sure how i should handle routing to the api controllers in the library. I created a CustomAPIMiddleware which is able to catch that i called "/api/crap" however i am not sure how i should forward the request over to CustomController in the library
public async Task Invoke(HttpContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
PathString matched;
PathString remaining;
if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matched, out remaining))
{
PathString path = context.Request.Path;
PathString pathBase = context.Request.PathBase;
context.Request.PathBase = pathBase.Add(matched);
context.Request.Path = remaining;
try
{
await this._options.Branch(context);
}
finally
{
context.Request.PathBase = pathBase;
context.Request.Path = path;
}
path = new PathString();
pathBase = new PathString();
}
else
await this._next(context);
}
After having done that i am starting to think i may have approached this in the wrong manner and should actually be trying to add it directly to the routing tables somehow. That being said i would like it if they could customize the endpoint that the custom controller reads from.
Update
The following does work. Loading and registering API Controllers From Class Library in ASP.NET core
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddApplicationPart(Assembly.Load(new AssemblyName("WebAPI")));
However i am really looking for a middlewere type solution so that users can simply add it and i can configure the default settings or they can change some of the settings. The above example would not allow for altering the settings.
app.UseCustomAPI("/api/crap");
Update from comment without Assembly
If i dont add the .AddApplicationPart(Assembly.Load(new AssemblyName("WebAPI")));
This localhost page can’t be found No webpage was found for the web address:
https://localhost:44368/api/Custom
To customise the routing for a controller at runtime, you can use an Application Model Convention. This can be achieved with a custom implementation of IControllerModelConvention:
public class CustomControllerConvention : IControllerModelConvention
{
private readonly string newEndpoint;
public CustomControllerConvention(string newEndpoint)
{
this.newEndpoint = newEndpoint;
}
public void Apply(ControllerModel controllerModel)
{
if (controllerModel.ControllerType.AsType() != typeof(CustomController))
return;
foreach (var selectorModel in controllerModel.Selectors)
selectorModel.AttributeRouteModel.Template = newEndpoint;
}
}
This example just replaces the existing template (api/[controller]) with whatever is provided in the CustomControllerConvention constructor. The next step is to register this new convention, which can be done via the call to AddMvc. Here's an example of how that works:
services.AddMvc(o =>
{
o.Conventions.Add(new CustomControllerConvention("api/whatever"));
});
That's all that's needed to make things work here, but as you're offering this up from another assembly, I'd suggest an extension method based approach. Here's an example of that:
public static class MvcBuilderExtensions
{
public static IMvcBuilder SetCustomControllerRoute(
this IMvcBuilder mvcBuilder, string newEndpoint)
{
return mvcBuilder.AddMvcOptions(o =>
{
o.Conventions.Add(new CustomControllerConvention(newEndpoint));
});
}
}
Here's how that would be called:
services.AddMvc()
.SetCustomControllerRoute("api/whatever");
This whole approach means that without a call to SetCustomControllerRoute, api/Custom will still be used as a default.
For this current project I am working on, we need to implement a web api. It needs to live inside the existing webforms project. And the specifications say we need to use Owin.
So after wiring everything up using: Microsoft.Owin, Microsoft.Owin.Host.SystemWeb, Microsoft.Owin.Hosting, Microsoft.Owin.Security
A proper startup class with the OwinStartupAttribute.
app.UseWebApi with a windsor IOC container.
Web api seems to work as expected.
Except for the fact that all requests made to the existing website also go through to webapi.
A bit more explanation.
We needed a LanguageMessageHandler : DelegatingHandler. After setting that class up we've started noticing that the breakpoint on 'SendAsync gets caught even when we are not requesting anything webApi related.
The older website shouldn't even have knowledge about this handler.
A bit code the clarify:
The startupclass:
[assembly: OwinStartupAttribute(typeof(Startup))]
public class Startup
{
public void Configuration(IAppBuilder app)
{
var container = ((IContainerAccessor)HttpContext.Current.ApplicationInstance).Container;
app.UseWebApi(container);
}
}
The UseWebApi extension:
public static void UseWebApi(this IAppBuilder app, IWindsorContainer container)
{
var config = new HttpConfiguration
{
DependencyResolver = new WindsorDependencyResolver(container)
};
//Web API Routes
config.MapHttpAttributeRoutes();
//Default to json when requested by browser
config.Formatters.JsonFormatter.MediaTypeMappings.Add(new RequestHeaderMapping("Accept", "text/html", StringComparison.InvariantCultureIgnoreCase, true, "application/json"));
//Add language handler
config.MessageHandlers.Add(new LanguageMessageHandler());
//Ensure initialized
config.EnsureInitialized();
//Start WebApi
app.UseWebApi(config);
}
So now we are trying to figure out why all the requests are handled by the LanguageMessageHandler and not just the requests that are made for webApi.
An example route:
[RoutePrefix("api/dossier")]
public class AdministrationsController : ApiController
{
//GET
[Route("{idtype}_{id}/administrations/planned/")] //?limit={maxdate}&nursingunit={nuid}
[HttpGet]
public IHttpActionResult Planned(string idtype, int id, [FromUri] int maxdate = 6, [FromUri] int? nuid = null)
{
return Ok();
}
}
Fixed by using a filter instead of a message handler.
Was wrongfully asuming that message handler was going to be executed after routing in the pipeline.
I'm just starting with Unity IOC, hoping someone will help. I need to be able to switch the dependencies in Unity at run time. I have two containers each for production and dev/test environments, "prodRepository" and "testRepository" defined in the web.config as follows:
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<alias alias="TestDataService" type="MyApp.API.Data.TestDataRepository.TestDataService, MyApp.API.Data" />
<alias alias="ProdDataService" type="MyApp.API.Data.ProdDataRepository.ProdDataService, MyApp.API.Data" />
<assembly name="MyApp.API.Data" />
<container name="testRepository">
<register type="MyApp.API.Data.IDataService" mapTo="TestDataService">
<lifetime type="hierarchical" />
</register>
</container>
<container name="prodRepository">
<register type="MyApp.API.Data.IDataService" mapTo="ProdDataService">
<lifetime type="hierarchical" />
</register>
</container>
</unity>
In the WebApiConfig class the Unit is configured as follows
public static void Register(HttpConfiguration config)
{
config.DependencyResolver = RegisterUnity("prodRepository");
//... api configuration ommitted for illustration
}
public static IDependencyResolver RegisterUnity(string containerName)
{
var container = new UnityContainer();
container.LoadConfiguration(containerName);
return new UnityResolver(container);
}
Just for test I created a simple controller and action to switch the configuration:
[HttpGet]
public IHttpActionResult SwitchResolver(string rName)
{
GlobalConfiguration.Configuration
.DependencyResolver = WebApiConfig.RegisterUnity(rName);
return Ok();
}
and I call it from a web browser:
http://localhost/MyApp/api/Test/SwitchResolver?rName=prodRepository
When I try to retrieve the actual data from the repositories via the API, at first it comes from "prodRepository", understandably, as that's how it is initialized in the code. After I switch it to "testRepository" from the browser, the data comes from the test repo as expected. When I switch it back to prodRepository, the API keeps sending me the data from the test repo.
I see in the controller that the GlobalConfiguration.Configuration .DependencyResolver changes the container and registrations to the ones specified in the URL query as expected, but it seems to change the configuration only once then stays at that configuration.
Ok, so this evil plan is what I came up with but as I am new to this I am probably going wrong direction altogether.
I need to be able to specify dynamically at run-time which container to use, hopefully without reloading the API. Does the above code make sense or what would you suggest?
It looks like you are going awry in many ways:
Using XML to configure a DI container is considered to be an obsolete approach.
Do you really want to access test data from your production environment and vice versa? Usually one environment is chosen through a configuration setting and the setting itself is changed upon deployment to each environment. And in that case, it makes sense to load the data service only 1 time at application startup.
If the answer to #2 is no, one way to get the job done easily and reliably is to use web.config transforms during deployment.
If the answer to #2 is yes, you can solve this by using a strategy pattern, which allows you to create all data services at startup and switch between them at runtime.
Here is an example of #4:
NOTE: WebApi is stateless. It doesn't store anything on the server after the request has ended. Furthermore, if your WebApi client is not a browser, you may not be able to use techniques such as session state to store which data provider you are accessing from one request to the next because this depends on cookies.
Therefore, having a SwitchResolver action is probably nonsensical. You should provide the repository on each request or otherwise have a default repository that can be overridden with a parameter per request.
Interfaces
public interface IDataService
{
void DoSomething();
bool AppliesTo(string provider);
}
public interface IDataServiceStrategy
{
void DoSomething(string provider);
}
Data Services
public class TestDataService : IDataService
{
public void DoSomething()
{
// Implementation
}
public bool AppliesTo(string provider)
{
return provider.Equals("testRepository");
}
}
public class ProdDataService : IDataService
{
public void DoSomething()
{
// Implementation
}
public bool AppliesTo(string provider)
{
return provider.Equals("prodRepository");
}
}
Strategy
This is the class that does all of the heavy lifting.
There is a GetDataService method that returns the selected service based on the passed in string. Note that you could alternatively make this method public in order to return an instance of IDataService to your controller so you wouldn't have to make two implementations of DoSomething.
public class DataServiceStrategy
{
private readonly IDataService[] dataServices;
public DataServiceStrategy(IDataService[] dataServices)
{
if (dataServices == null)
throw new ArgumentNullException("dataServices");
this.dataServices = dataServices;
}
public void DoSomething(string provider)
{
var dataService = this.GetDataService(provider);
dataService.DoSomething();
}
private IDataService GetDataService(string provider)
{
var dataService = this.dataServices.Where(ds => ds.AppliesTo(provider));
if (dataService == null)
{
// Note: you could alternatively use a default provider here
// by passing another parameter through the constructor
throw new InvalidOperationException("Provider '" + provider + "' not registered.");
}
return dataService;
}
}
See these alternate implementations for some inspiration:
Best way to use StructureMap to implement Strategy pattern
Factory method with DI and Ioc
Unity Registration
Here we register the services with Unity using a container extension rather than XML configuration.
You should also ensure you are using the correct way to register Unity with WebApi as per MSDN.
public static IDependencyResolver RegisterUnity(string containerName)
{
var container = new UnityContainer();
container.AddNewExtension<MyContainerExtension>();
return new UnityResolver(container);
}
public class MyContainerExtension
: UnityContainerExtension
{
protected override void Initialize()
{
// Register data services
// Important: In Unity you must give types a name in order to resolve an array of types
this.Container.RegisterType<IDataService, TestDataService>("TestDataService");
this.Container.RegisterType<IDataService, ProdDataService>("ProdDataService");
// Register strategy
this.Container.RegisterType<IDataServiceStrategy, DataServiceStrategy>(
new InjectionConstructor(new ResolvedParameter<IDataService[]>()));
}
}
Usage
public class SomeController : ApiController
{
private readonly IDataServiceStrategy dataServiceStrategy;
public SomeController(IDataServiceStrategy dataServiceStrategy)
{
if (dataServiceStrategy == null)
throw new ArgumentNullException("dataServiceStrategy");
this.dataServiceStrategy = dataServiceStrategy;
}
// Valid values for rName are "prodRepository" or "testRepository"
[HttpGet]
public IHttpActionResult DoSomething(string rName)
{
this.dataServiceStrategy.DoSomething(rName);
return Ok();
}
}
I highly recommend you read the book Dependency Injection in .NET by Mark Seemann. It will help lead you down the correct path and help you make the best choices for your application as they apply to DI, which is more than what I can answer on a single question on SO.
This question is very similar to what I want to know. I've got a web api service on an azure cloud service with Application Insights configured. On the request information portal, that is generated automatically, I want to add a custom http header that's a part of the request into the information that is being logged with each request. The question is how do I do this?
I've tried using a telemetry initializer like below, but this fails (as in I don't see the information on the portal). I also added this in the global.asax
TelemetryConfiguration.Active.TelemetryInitializers.Add(propertyTelemetryInitializer);
public class PropertyTelemetryInitializer : ITelemetryInitializer
{
private readonly HttpContext httpContext;
public PropertyTelemetryInitializer(HttpContext httpContext)
{
this.httpContext = httpContext;
}
public void Initialize(ITelemetry telemetry)
{
this.AddTelemetryContextPropertFromContextHeader(telemetry, "xyz");
this.AddTelemetryContextPropertFromContextHeader(telemetry, "abc");
this.AddTelemetryContextPropertFromContextHeader(telemetry, "123");
}
private void AddTelemetryContextPropertFromContextHeader(ITelemetry telemetry, string headerKey)
{
var requestTelemetry = telemetry as RequestTelemetry;
telemetry.Context.Properties[headerKey] = this.httpContext.Request.Headers[headerKey] ?? string.Empty;
telemetry.Context.Properties[headerKey] = this.httpContext.Request.Headers[headerKey] ?? string.Empty;
}
}
Also is there a way to do this from the controller method itself? Something similar to the below (note: the below does not work)?
[Route("api/Something")]
[HttpGet]
[ResponseType(typeof(Something))]
public async Task<Something> GetSomething()
{
var requestTelemetry = new RequestTelemetry();
this.AddCustomHeadersToRequestTelemetry(requestTelemetry);
var result = await this.Service.GetSomethingAsync();
requestTelemetry.Properties["result"] = result.ToString();
return TypeMapper.Map<Model.Something, Something>(result);
}
/// <summary>
/// Adds the custom headers to request telemetry.
/// </summary>
/// <param name="controller">The controller.</param>
/// <param name="requestTelemetry">The request telemetry.</param>
public static void AddCustomHeadersToRequestTelemetry(this ApiController controller, RequestTelemetry requestTelemetry)
{
if (controller == null)
{
throw new ArgumentNullException("controller");
}
if (requestTelemetry == null)
{
throw new ArgumentNullException("requestTelemetry");
}
requestTelemetry.Context.Properties["abc"] = controller.Request.GetABCFromHeader();
requestTelemetry.Context.Properties["xyz"] = controller.Request.GetXYZFromHeader();
requestTelemetry.Context.Properties["123"] = controller.Request.Get123FromHeader();
}
Using TelemetryInitializers is the right solution. Some comments:
var requestTelemetry = telemetry as RequestTelemetry;: you do not use requestTelemetry after that. I guess you wanted to check for null.
Adding telemetry initializer in the Active configuration should be fine. You can also consider moving it to the applicationinsights.config
Custom properties do not show up in the portal immediately. Have you tried to reopen IE after some time and check your request again?
Can you debug? Do you see that you get in your tememetry initializer? Do you see any AI specific traces in search?
Regarding your second question. Right now telemetry initializers are the only (official) way to get to the autogenerated RequestTelemetry (which is actually in the HttpContext). There are plans to make most of the classes in web public and eventually open source it. But there is no ETA yet. If you create and track request yourself you can add custom properties as you mentioned.
UPDATE: Starting from 2.0.0-beta3 autogenerated request telemetry is accessible though HttpContext extension method: System.Web.HttpContextExtension.GetRequestTelemetry