Trying to stub Server.MapPath with MvcContrib Test helpers and Rhino Mocks 3.5 - rhino-mocks

I'm using MvcContrib's test helpers and Rhino Mocks 3.5 to test an ASP.NET MVC action method. I build my fake controller like so:
var _builder = new TestControllerBuilder();
_builder.InitializeController(_controller);
So I get a fake controller that contains fake HTTP Server etc.
I'm then trying to stub the Server.MapPath method like so
controller.Server.Stub(x => x.MapPath(Arg<string>.Is.Anything)).Return("/APP_DATA/Files/");
but in my method under test the call to Server.MapPath("/APP_DATA/Files/") returns null.
This is the test
const string STOCK_NUMBER_ID = "1";
const string FULL_FILE_PATH = "App-Data/Files";
var controller = CreateStockController();
_uploadedFileTransformer.Stub(x => x.ImageBytes).Return(new byte[10]);
_uploadedFileTransformer.Stub(x => x.ConvertFileToBytes(FULL_FILE_PATH)).Return(true);
controller.Server.Stub(x => x.MapPath(Arg<string>.Is.Anything)).Return("/App_Data/Files/");
controller.AddImage(Guid.NewGuid(), STOCK_NUMBER_ID);
What I am missing?

Old post but I was searching for this and I found a solution, MvcContrib's TestHelper probably got it fixed because for me it's working.
_builder.HttpContext.Server.Stub(s => s.MapPath("~/" + filepath)).Repeat.Once().Return(mapedPath);

Looks like this is a bug in MVCContrib (at least with what I have on my machine -- v1.0.0.0). When setting up the controller context, it's using Rhino.Mocks record/replay mode, but (and this is the bug), it doesn't put the HttpServer mock into replay mode. It puts everything else in replay mode but not that one.
So a quick fix is to do:
controller.Server.Replay();
As part of your "arrange" section of your test. Then it works fine.

Related

How to effectively set the ServerUrl in the NSwag settings?

I have a working ASP .NET 5 application with a REST API and a Swagger interface using the NSwag library (so not Swashbuckle as many people use instead). I have found (or at least I have thought so) a way to set the server url. There is a property to set this. Here is my code:
app.UseSwaggerUi3(settings =>
{
settings.ServerUrl = "https://XXXXX.YYYYY.com/ZZZZ";
settings.TransformToExternalPath = (url, request) =>
{
// Get the UI to properly find the relative path of the swagger json instead of absolute path.
string outputUrl;
if (url.EndsWith(".json") || request.Path.ToString().EndsWith("/")) outputUrl = ".." + url;
else outputUrl = request.PathBase + "." + url;
return outputUrl;
};
});
However, when running my application and using the swagger interface, the set server url is not used at all... It simply refers to same host as it does without setting it. I know it is possible to change the routing behavior of swagger when needed (as explained here and I also used this code) but that basically solves the problem of not being able to find the swagger json. However, I did not find a way to effectively set the server url. When I use the code shown in this post, the request url when executing a request refers to the same url when not using it.
How can I fix this? How to effectively set the server url? This is so strange. I looks like the property does not do anything at all.
For me it seems to work well using the Host property in PostProcess. I set it to null to get the UI working for all incoming host names.
I'm on Owin version but same looks like it should work in 5/6 as well.
RouteTable.Routes.MapOwinPath("swagger", app =>
{
..
app.UseSwaggerUi3(typeof(Global).Assembly, c => {
// Fix to make UI work regardless of hostname serving
c.PostProcess = document => {
document.Host = null;
};
});
});
The ServerUrl property looks like it's only used as RedirectUrl in an eventual OAuth2 flow.

Prevent ASP.NET Core discovering Controller in separate assembly

I've got an ASP.NET Core API that references a nuget package that contains a Controller.
By default, this controller is registered and can respond to requests. However, I only want to add this in certain circumstances - e.g. if it's in the DEV environment.
My Startup looks like this:
services.AddControllers()
.AddMvcOptions(cfg => {
cfg.Filters.Add(new CustomExceptionFilterAttribute())
});
I expected I'd need to call AddApplicationPart(typeof(ClassInPackage).Assembly) after calling AddCointrollers to register this controller?
Can someone advise a way I can enable / disable the registration of this controller?
Ok, I've found a solution - remove the ApplicationPart that contains the Controller. Any other dependencies in the assembly can still be used.
In Startup.cs / wherever you do your IoC:
if(hideControllerFromOtherAssembly)
{
var appPartManager = (ApplicationPartManager)services.FirstOrDefault(a => a.ServiceType == typeof(ApplicationPartManager)).ImplementationInstance;
var mockingPart = appPartManager.ApplicationParts.FirstOrDefault(a => a.Name == "MyMockLibrary.Namespace");
if(mockingPart != null)
{
appPartManager.ApplicationParts.Remove(mockingPart);
}
}
You can also manipulate ApplicationParts via the extension method:
AddMvc().ConfigureApplicationPartManager()
This wasn't suitable for me as I'd written an extension method in my nuget package
https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts?view=aspnetcore-5.0

How do I call DotNet Core API HealthCheck probes within Controller instead of setting up in Ctartup.cs

I would like to setup Microsoft.Extensions.Diagnostics.HealthChecks so that I can setup response body within controller instead of standard setup in Startup.cs. Is this possible? If so, how can I achieve this?
The thought here is that I would like control over the response payload setter logic, and to do this within a controller action/method.
Online contains clear instructions on how to setup healthcheck probes, but all examples show the setup occuring within Startup.cs.
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-3.1
Are probes strickly setup within startup only? Is this a constraint?
My understanding is that the healtcheck library is middleware that will terminate request from going further down the middleware pipeline, and that perhaps removing the middleware will mean that whatever was setup in startup must now be setup within controller action method.
Is it possible to setup healthcheck probes within controller action methods? Answer is No
You can use app.UseHealthChecks to have custom control on health check enpoint
app.UseHealthChecks("/health-detailed", new HealthCheckOptions
{
ResponseWriter = (context, result) =>
{
context.Response.ContentType = "application/json";
var json = new JObject(
new JProperty("status", result.Status.ToString()),
new JProperty("duration", result.TotalDuration),
new JProperty("results", new JObject(result.Entries.Select(pair =>
new JProperty(pair.Key, new JObject(
new JProperty("status", pair.Value.Status.ToString()),
new JProperty("tags", new JArray(pair.Value.Tags)),
new JProperty("description", pair.Value.Description),
new JProperty("duration", pair.Value.Duration),
new JProperty("data", new JObject(pair.Value.Data.Select(
p => new JProperty(p.Key, p.Value))))))))));
context.Response.ContentType = MediaTypeNames.Application.Json;
return context.Response.WriteAsync(
json.ToString(Formatting.Indented));
}
});
TL&DR: Use this Library: https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks if you want somthing already created.
This website provides a ton of fully functional healthchecks for different services such as PostGres, Redis, S3, and etc.

Netcore how to remove endpoints/routes at runtime

Is there a way to remove registered routes in net core web api project?
So I'm dynamically adding controllers in a net core web api project, the controller class code is not part of the project but dynamically loaded, compiled and add to the project at runtime
//code that compiles the c# class(controller)
var compiledAssembly= CompileHelper.Compile(csharpCode)
using (var controllerAssemblyMs = new MemoryStream(compiledAssembly))
{
var assemblyLoadContext = new SimpleAssemblyLoadContext();//inherits AssemblyLoadContext
var dynamicControllers = new MvcAssemblyPart(controllerAssemblyMs);
Services.AddControllersWithViews().ConfigureApplicationPartManager(apm =>
apm.ApplicationParts.Add(dynamicControllers));
}
so any new Endpoints/Routes are registered.
the problem is that because routes have been registered every time it compiles the code, if I change Get action to Post action, compile before and after, the endpoints end up in an erroneous state,
AmbiguousMatchException: The request matched multiple endpoints. Matches:
DynamicCodeProject.Controllers.DynamicallyAddedController.Post (string)
DynamicCodeProject.Controllers.DynamicallyAddedController.Get (string)
in which case I have to restart the application,
is it possible to remove routes/endpoints at runtime so I don't have to restart the application?
I has a same problem. My solution was remove previous controller from apm.ApplicationParts
var parts = _partManager.ApplicationParts.Where((x) => ((AssemblyPart)x).Assembly.GetName().Name == compiledAssembly.GetName().Name).ToList();
foreach(var part in parts)
{
apm.ApplicationParts.Remove(part);
}
apm.AddApplicationPart(compiledAssembly);
this work in asp.net core 5.0

AssumeDefaultVersionWhenUnspecified is not working as expected

I have been using asp net core versioning component for my WebAPI. Need your help in understanding how AssumeDefaultVersionWhenUnspecified is working. (tried searching for documentation, but couldn't find one)
My startup looks like below
services.AddApiVersioning(o => {
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(2, 0);
o.ApiVersionReader = new UrlSegmentApiVersionReader();
});
When the route attribute is something like below
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/values")]
[ApiController]
public class ValuesV2Controller : ControllerBase
{
...
}
The above route works only when the api version is specified. ie: http://localhost:55401/api/v2/values
If I call like http://localhost:55401/api/values, getting 404 error
My question is this... How AssumeDefaultVersionWhenUnspecified works. Wouldn't it ignore the version in Route? Looks like Route attribute takes precedence over AssumeDefaultVersionWhenUnspecified. If I choose QueryString or Header versioning and when the Route looks like
[ApiVersion("2.0")]
[Route("api/values")]
the default routing reaches the API
Am I missing anything or is my understanding wrong? How shall I achieve default routing to the latest version API using url versioning?
I am also trying to achieve the same functionality. By looking into [https://github.com/Microsoft/aspnet-api-versioning/issues/351#issuecomment-425106940]
I am assuming that we can't achieve default API version AssumeDefaultVersionWhenUnspecified functionality with only a single style of versioning uses a URL segment [Route("api/v{version:apiVersion}/[controller]")]
We have to define two routes separately as follow
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]
and to hide the two implementations from swagger you can achieve using this link
Summarizing the the solution from the github issue linked by Athi S, here's what you need to do :
In ConfigureServices inside Startup.cs file :
services.AddApiVersioning(o =>
{
o.AssumeDefaultVersionWhenUnspecified = true;
o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);
// o.DefaultApiVersion = new ApiVersion(1, 0);
});
You can optionally set ApiVersionSelector to a new instance of CurrentImplementationApiVersionSelector. What this does is, it automatically selects the highest api version registered in controllers. E.g. A controller decorated with [ApiVersion("1.2")] takes precedence over [ApiVersion("1.1")].
If you want to specify default api version explicitly, you can do so by leaving ApiVersionSelector to DefaultApiVersionSelector and setting DefaultApiVersion to your required api version.
In your controllers :
Register the required routes by decorating your controllers with the given Route attributes
[Route("api/[controller]")]
Or if you want the api to work both with and without the api version number specified, you can do so by declaring two routes for the controller.
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]