In a Web API 2 web service I currently have a custom authentication handler set up for the default route in WebApiConfig.vb, the default route looks like this:
config.Routes.MapHttpRoute(
name:="DefaultApi",
routeTemplate:="api/{controller}/{id}",
defaults:=New With {.id = RouteParameter.Optional},
constraints:=Nothing,
handler:=New AuthenticationHandler() With {.InnerHandler = New Dispatcher.HttpControllerDispatcher(config)}
)
And the AuthenticationHandler class looks like this:
Imports System.Net
Imports System.Net.Http
Imports System.Threading.Tasks
Imports System.Runtime.Caching
Imports System.Text
Imports System.Security.Cryptography
Public Class AuthenticationHandler
Inherits DelegatingHandler
Protected Overrides Function SendAsync(request As HttpRequestMessage, cancellationToken As Threading.CancellationToken) As Threading.Tasks.Task(Of HttpResponseMessage)
Dim response As New HttpResponseMessage
Dim lblnIsAuthenticated As Boolean = False
Dim tsc As New TaskCompletionSource(Of HttpResponseMessage)
'// Do the authentication work here...
if not lblnIsAuthenticated then
'// Set the response
response.StatusCode = HttpStatusCode.BadRequest
response.Headers.Add("Status-Message", "Authentication failed.")
tsc.SetResult(response)
Return tsc.Task
end if
Return MyBase.SendAsync(request, cancellationToken)
End Function
When I upgraded this service to Web API 2 I wanted to take advantage of http attribute routing, so I added config.MapHttpAttributeRoutes() to WebApiConfig.vb and set up some of the controllers to use the Route and RoutePrefix attributes. The problem is that this routing method doesn't take advantage of the custom authentication handler. Is there any way to implement this same authentication handler for the http attribute routing? I've searched around for a solution to this but don't see how to make this work. Or do I need to just do standard routes?
UPDATE --
I also have some other routes in WebApiConfig.vb that have a different authentication handler on them so I don't think I can apply one handler globally.
You should be able to add your handler to GlobalConfiguration.Configuration.MessageHandlers; unless I'm mistaken the handlers added there are always invoked for all the requests. Unless you're trying to do something different?
Related
I have a separate class library project(.NET Framework 4.8) to access the DB. This class library is written in Visual Basic(VB).
Recently, due to a new requirement, I had to add a web API layer into my solution to expose the data through endpoints.
I created a new .NET Core 2.1 Web API project in my existing solution. I configured the app settings in appSettings.json in .NET Core instead of App.config in .NET Framework as per the specifications.
Now the below code fails (I am not able to access my configuration settings).
Public connectionString As String = System.Configuration.ConfigurationManager.AppSettings("ConnectionString")
Error: system.typeinitializationexception
Note: This code was previously working. Not able to access the configuration settings.
Alternative tried: I tried to create a .NET Framework Web API (v4.8) instead of .NET Core Web API, and it worked. So, please provide me a solution to access the appSettings.json from .NET Core Web API project to .NET Framework Class Library (v4.8) which is written in VB.
How to access appSettings.json which belongs to a .NET Core Web API project from a .NET Framework Class Library project in the same solution
As we know, ASP.NET Core use different configuration settings. To achieve your above requirement, you can try following approaches:
Approach 1: modify your Class Library method(s) to accept additional parameter, then you can read ConnectionString etc from Web.config or appsettings.json and pass it as parameter while you call method within corresponding Apps.
Approach 2: modify and extend your Class Library to make it works with ASP.NET Core configuration settings, like below.
Imports System.Configuration
Imports Microsoft.Extensions.Configuration
Public Class DbGeneric
Private ReadOnly _configuration As IConfiguration
Public Sub New(ByVal configuration As IConfiguration)
_configuration = configuration
End Sub
Public Sub New()
End Sub
Public Function GetAll() As String
Dim connectionString As String = ConfigurationManager.ConnectionStrings("DefaultConnection").ConnectionString
Return connectionString
End Function
Public Function GetAllForCore() As String
Dim connectionString As String = _configuration("ConnectionStrings:DefaultConnection").ToString()
Return connectionString
End Function
End Class
In ASP.NET app
var dbGeneric = new MyClassLibraryVB.DbGeneric();
var con_str = dbGeneric.GetAll();
In ASP.NET Core app
public class ValuesController : ControllerBase
{
private readonly IConfiguration Configuration;
public ValuesController(IConfiguration configuration)
{
Configuration = configuration;
}
public IActionResult GetAll()
{
var dbGeneric = new MyClassLibraryVB.DbGeneric(Configuration);
var con_str = dbGeneric.GetAllForCore();
ConnectionStrings in appsettings.json
"ConnectionStrings": {
"DefaultConnection": "{conection_string_for_netcore}"
}
Test Result
I am trying to setup an "external" web api that will be able to receive in large http posts (+1GB) and forward the stream to another "internal" web api that writes the request contents to a file. I have model my implementation based on examples of using a custom WebHostBufferPolicySelector and using the UseBufferedInputStream method in the controller method. It works as expected when using IIS Express, no significate increase in memory footprint, but as soon as my code is deployed to IIS, the memory footprint is substantial and results in OOM.
I have put tracing statements in my controller methods and in my WebHostBufferPolicySelector.UseBufferedInputStream, and have verified that UseBufferedInputStream always is returning false and that my controller methods are getting hit. The only difference that I noticed is that when I debug, the time stamps between UseBufferedInputStream and my controller method are very close. Where hosted on IIS, the time stamps are very far apart, suggesting that something in between when UseBufferedInputStream is called and my controller method is called is buffering up the request entirely.
I am looking for some tips on to find out what that is causing the request to get buffered and how for it not to buffer and using streaming all the way.
Client is coming at the external web api with a content type of application/octet-stream with Transfer Encoding of Chucked.
Used to build out implementation
https://forums.asp.net/t/2018289.aspx?Web+API2+WebHostBufferPolicySelector+UseBufferedInputStream+override
https://www.strathweb.com/2012/09/dealing-with-large-files-in-asp-net-web-api/
Proxy Web Api Controller Method
<HttpPost, Route("postLargeFile")>
Protected Overridable Async Function PostLargeFile() As Threading.Tasks.Task(Of IHttpActionResult)
Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyProxyController)}", "Started {0}", NameOf(MyProxyController.PostLargeFile))
Dim internalHttpClient As HttpClient
Dim fowardingContent As StreamContent = Nothing
Dim fowardingMessage As HttpRequestMessage = Nothing
Dim fowardingResponse As HttpResponseMessage = Nothing
Dim externalResponse As HttpResponseMessage = Nothing
Try
internalHttpClient = New HttpClient()
internalHttpClient.BaseAddress = "https://myinternalService.com"
fowardingMessage = New HttpRequestMessage(HttpMethod.Post, "https://myinternalService.com/saveLargeFile")
fowardingContent = New StreamContent(HttpContext.Current.Request.GetBufferlessInputStream(True))
CopyContentHeaders(Request.Content, fowardingContent)
fowardingMessage.Headers.TransferEncodingChunked = True
fowardingMessage.Content = fowardingContent
fowardingResponse = Await internalHttpClient.SendAsync(fowardingMessage, HttpCompletionOption.ResponseHeadersRead)
externalResponse = New HttpResponseMessage(fowardingResponse.StatusCode)
externalResponse.Content = New StreamContent(Await fowardingResponse.Content.ReadAsStreamAsync)
CopyContentHeaders(fowardingResponse.Content, externalResponse.Content)
Return New Results.ResponseMessageResult(externalResponse)
Catch ex As Exception
Return InternalServerError(ex)
Finally
Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyProxyController)}", "Finished {0}", NameOf(MyProxyController.PostLargeFile))
End Try
End Function
Internal Web Api Controller Method
<HttpPost, Route("saveLargeFile")>
Protected Overridable Async Function SaveLargeFile() As Threading.Tasks.Task(Of IHttpActionResult)
Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyInternalController)}", "Started {0}", NameOf(MyInternalController.PostLargeFile))
Dim bufferlessStream As IO.Stream
Dim fowardingContent As StreamContent = Nothing
Try
bufferlessStream = HttpContext.Current.Request.GetBufferlessInputStream()
Using fileStream As IO.FileStream = IO.File.Create("MyFile.txt")
bufferlessStream.CopyTo(fileStream)
fileStream.Flush()
End Using
Return New Results.StatusCodeResult(Net.HttpStatusCode.Created, Me)
Catch ex As Exception
Return InternalServerError(ex)
Finally
Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyInternalController)}", "Finished {0}", NameOf(MyInternalController.PostLargeFile))
End Try
End Function
Policy Selector Configuration
Public Class MyBufferPolicySelector
Inherits Http.WebHost.WebHostBufferPolicySelector
Public Property Tracer As ITraceWriter
Public Overrides Function UseBufferedInputStream(hostContext As Object) As Boolean
UseBufferedInputStream = False
Tracer?.Info(Nothing, $"{Me.GetType.Namespace}.{NameOf(MyBufferPolicySelector)}", "{0} UseBufferedInputStream={1}", HttpContext.Current?.Request?.Url?.AbsoluteUri, UseBufferedInputStream)
Return UseBufferedInputStream
End Function
End Class
WebApiConfig for both Internal and External Web APIs
Public Module WebApiConfig
Public Sub Register(ByVal config As HttpConfiguration)
Dim tracer As SystemDiagnosticsTraceWriter
' Web API configuration and services
' Web API routes
config.MapHttpAttributeRoutes()
tracer = config.EnableSystemDiagnosticsTracing
tracer.IsVerbose = True
tracer.MinimumLevel = Tracing.TraceLevel.Debug
GlobalConfiguration.Configuration.Services.Replace(GetType(IHostBufferPolicySelector), New MyBufferPolicySelector() With {.Tracer = tracer})
End Sub
End Module
I was able to figure out what was causing the the buffering in IIS. The below link lead me to the uploadReadAheadSize setting in IIS. This was maxed out. So this would cause IIS to fully read in/buffer in the request before passing it into the module where the web api pipeline exists (web api controllers). After setting it to the default, I saw my large file posts not get buffered, the app pool memory footprint remained low, no more out of memory exceptions, and a large performance boost. Great!
But now I have the same issue as described in the below link. When SSL is required, set in IIS, which it is required in our non development environments, the uploadReadAheadSize needs to be increased so the ssl can work in the ssl module I guess. It might have to do with some SSL renegotiation.
Can anybody describe a way to prevent the buffering in SSL to keep the memory footprint low and prevent out of memory exceptions for large http posts?
Large file upload when using ssl and client certificates (uploadReadAheadSize) but dont want all data to be readahead
I am using System.Web.Mvc.Controller for the UI and System.Web.Http.ApiController for the API in prototyping a web interface for large ERP application. I have figured out a way to make the UI somewhat extensible with the question Deploying un-compiled ASP.NET MVC Razor application. Now I am wondering, due to the strict nature of ApiController if there is some other class I should be considering for providing an open-ended interface for defining custom API transactions. Or is there some way to use ApiController in a more open-ended way where parameter count and type may be varied... perhaps by accepting an object parameter?
For Web API, you could try implementing a custom action selector using IHttpActionSelector interface:
public class CustomActionSelector : IHttpActionSelector
{
public override HttpActionDescriptor SelectAction(HttpControllerContext context)
{
var method = GetMethod(context);
return new ReflectedHttpActionDescriptor(GetController(method), method);
}
private MethodInfo GetMethod(HttpControllerContext context)
{
// Locate the target method using the extensibility framework of your choice
// (for example, MEF, pure reflection, etc.)
}
private HttpControllerDescriptor GetController(MethodInfo method)
{
return new HttpControllerDescriptor()
{
ControllerName = method.DeclaringType.Name,
ControllerType = method.DeclaringType
};
}
}
To register your new action selector place the following in your global.asax file under Application_Start:
var config = GlobalConfiguration.Configuration;
config.Services.Replace(typeof(IHttpActionSelector), new CustomActionSelector());
Hope this helps.
To make an ASP.NET web application extensible is very straightforward because ASP.NET searches the bin directory for controller classes in all assemblies there. So if the party providing customizations can simply compile their code into a DLL and drop it into the bin directory, your web application will pick up all their controllers as well as the controllers from the standard delivery. As an example, I created the following class in a standalone DLL that referenced System.Web.Http and System.Web.Mvc:
Public Class CustomTestController
Inherits ApiController
Public Function GetValues() As IEnumerable(Of String)
Return New String() {"value1", "value2"}
End Function
End Class
I simply compiled it and copied it to the bin directory of the location where my web application was deployed, and then I could access http://localhost/MyApplication/api/CustomTest/ and get back value1 and value2 in the expected response.
I have a MVC 4 app in which i am wanting to use the web api to get my data. EDIT- this is a Single Page Application that started out with the Hot Towel Template. The problem is that I get the 404 resource not found when i try to call the controller from JSON. Here is my Controller-
Public Class CAApprovalController
Inherits ApiController
Public Function GetValues() As IEnumerable(Of String)
Return New String() {"value1", "value2"}
End Function
End Class
Here is my JSON call-
function getallCertificates() {
$.getJSON('api/CAApproval', function (data) {
allCertificates([]);
var temp = allCertificates();
data.forEach(function (p) {
var certificate = new Certificate(p.ClientID, p.RequestDate, p.UserName, p.StatusDescription, p.StatusCode, p.StatusDesc, p.CEOUserName);
temp.push(certificate);
});
allCertificates.valueHasMutated();
return allCertificates();
});
}
Here is the webapiconfig-
Public Class WebApiConfig
Public Shared Sub Register(ByVal config As HttpConfiguration)
config.Routes.MapHttpRoute( _
name:="DefaultApi", _
routeTemplate:="api/{controller}/{id}", _
defaults:=New With {.id = RouteParameter.Optional} _
)
'To disable tracing in your application, please comment out or remove the following line of code
'For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing()
'Use camel case for JSON data.
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = New CamelCasePropertyNamesContractResolver()
End Sub
End Class
I am new to MVC and especially web api, and am thinking it is a newbie issue. Just not sure what the problem is. Is there a configuration or something i am missing? The project was created as a MVC 4 / web api application.
Found the problem at last. Turns out that Breezejs was the problem. My app is a single page application, and Breeze was one of the components of my SPA app (Breeze was installed as part of the Hot Towel template i was using). Not sure why, but when i uninstalled Breeze, the controllers i added to the project became visible. Something in the breeze scripts hi-jack the api routing.
Are you hosting your web api in IIS or running it from Visual Studio. If IIS, /LucasNetApp/api/caaproval. If Visual Studio, /api/caaproval.
You realize that "api/..." means "from the current relative location" right?
You probably want "/api/..." in your ajax call.
EDIT:
It's better to use a Url Helper.
$.getJSON('#Url.HttpRouteUrl("DefaultApi", new { controller = "CAApproval" })', function (data) {
I've a service operation which I marked with the Authenticate attribute
[Authenticate]
[Route("/route/to/service", "POST")]
public class OperationA: IReturn<OperationAResponse>
{
...
}
The method IsAuthorized of the AuthProvider is called correctly when I call the service using the REST URL or using JsonServiceClient inside a unit test but is not called if I call the service from ASP.NET code behind (not MVC controller).
I don't use IoC to resolve the service inside my code behind but I use this code...
MyService service = AppHostBase.Instance.Container.TryResolve<MyService>();
service.Post(operationA);
Is there something I'm missing?
Thank you for your attention.
Just to clarify:
I don't use IoC to resolve the service inside my code behind but I use this code...
MyService service = AppHostBase.Instance.Container.TryResolve<MyService>();
You are using the IOC here, i.e. resolving an auto-wired instance of MyService from ServiceStack's IOC.
If you're service doesn't make use of the HTTP Request or Response objects than you can treat it like any normal class and call C# methods. If the service does (e.g. Auth/Registration) then you will also need to inject the current HTTP Request Context as well.
The CustomAuthenticationMvc UseCase project has an example of how to do this:
var helloService = AppHostBase.Resolve<HelloService>();
helloService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var response = (HelloResponse)helloService.Any(new Hello { Name = "World" });