I would like to test one method in controller which is retrieving value from IHttpContextAccessor.HttpContext.Connection.RemoteIpAddress.
Is it possible to customize WebApplicationFactory so one can set fixed ip address?
The workaround would be to wrap the expression above into service so it can be mocked.
Thanks
Test.cs
var ipAddress = IPAddress.Parse("192.168.1.123");
var factory = new WebApplicationFactory<Startup>();
// ?
using var client = factory.CreateClient();
var response = await client.GetAsync(new Uri("https://localhost/test"), UriKind.Relative));
var ipAddress = await response.Content.ReadAsStringAsync();
TestController.cs
private readonly IHttpContextAccessor _httpContextAccessor;
public TestController(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
[HttpGet("test")]
public async Task<IActionResult> GetTest()
{
var ipAddressObj = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress;
var ipAddress = ipAddressObj?.ToString();
return Ok(ipAddress);
}
Try this:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Moq;
using System.Net;
using WebApplication.Api.Controllers;
using Xunit;
namespace WebApplication.Api.Tests
{
public class TestControllerTests
{
[Fact]
public async void IpAdressResult()
{
var ipAddress = "192.168.1.123";
var accessorMcok = new Mock<IHttpContextAccessor>();
accessorMcok.Setup(a => a.HttpContext.Connection.RemoteIpAddress).Returns(IPAddress.Parse(ipAddress));
var controller = new TestController(accessorMcok.Object);
var result = (OkObjectResult)(await controller.GetTest());
Assert.Equal(ipAddress, result.Value);
}
}
}
Related
I am upgrading my C# function app from .net 3.1 to 6.0`.
When I run my test cases, I found that, 1 of my test case failed with the below error.
Castle.DynamicProxy.InvalidProxyConstructorArgumentsException : Can not instantiate proxy of class: System.Net.HttpWebRequest. Could not find a parameterless constructor.
Basically, I am trying to mock HttpWebRequest and below is my piece of code for that.
var httpWebRequest = new Mock<HttpWebRequest>();
It is working fine in .Net 3.1. I am using Moq version 4.16.1 in both the projects.
I spent a fair bit of time when .Net 6 was initially released getting my Unit Test suite established. Here's how I do it using the same Moq version 4.16.1:
The Unit Tests get a Moq HttpClientFactory from the BaseClass:
public class UnitTests : BaseUnitTest
{
[Fact]
public async Task Should_Return_GetSomethingAsync()
{
// Arrange
IHttpClientFactory httpClientFactory = base.GetHttpClientFactory(new Uri("ExternalWebsiteUrlToMockTheResponse"), new StringContent("A Mock Response JSON Object"));
YourService yourService = new YourService(httpClientFactory);
// Act
Something something = yourService.GetSomethingAsync().Result;
// Assert
Assert.IsType<Something>(Something);
//..
}
In a BaseUnitTest.cs Class:
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Moq.Protected;
public class BaseUnitTest
{
public IHttpClientFactory GetHttpClientFactory(Uri uri, StringContent content, HttpStatusCode statusCode = HttpStatusCode.OK)
{
Mock<HttpMessageHandler> httpMsgHandler = new Mock<HttpMessageHandler>();
httpMsgHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", new object[2]
{
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
}).ReturnsAsync(new HttpResponseMessage
{
StatusCode = statusCode,
Content = content
});
HttpClient client = new HttpClient(httpMsgHandler.Object);
client.BaseAddress = uri;
Mock<IHttpClientFactory> clientFactory = new Mock<IHttpClientFactory>();
clientFactory.Setup((IHttpClientFactory cf) => cf.CreateClient(It.IsAny<string>())).Returns(client);
return clientFactory.Object;
}
Your Service Class or Controller:
public class YourService : IYourService
{
private readonly IHttpClientFactory _clientFactory;
private readonly HttpClient _client;
public YourService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
_client = _clientFactory.CreateClient("YourAPI");
}
public async Task<Something> GetSomethingAsync()
{
using (var request = new HttpRequestMessage(HttpMethod.Post, _client.BaseAddress))
{
request.Content = new StringContent($#"{{""jsonrpc"":""2.0"",""method"":""Something"",""params"": [""{SomethingHash}""],""id"":1}}");
using (var response = await _client.SendAsync(request))
{
//System.Diagnostics.Debug.WriteLine(response?.Content.ReadAsStringAsync()?.Result);
if (response.IsSuccessStatusCode)
{
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
var options = new JsonSerializerOptions { IncludeFields = true };
var something = await JsonSerializer.DeserializeAsync<Something>(responseStream, options);
// Check if the transactions from the address we're looking for...
if (something != null)
{
if (something.result?.from == address)
{
return something;
}
} } }
else {
string exceptionMsg = $"Message: {response.Content?.ReadAsStringAsync()?.Result}";
throw new YourGeneralException(response.StatusCode, exceptionMsg);
}
}
}
return null;
}
}
In your Program.cs
builder.Services.AddHttpClient("YourAPI", c =>
{
c.BaseAddress = new Uri("ExternalWebsiteUrlToMockTheResponse");
c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
c.DefaultRequestHeaders.UserAgent.TryParseAdd("Your Agent");
});
You can expand the BaseUnitTest.ccs class to have chained tests as well.
Both HttpWebRequest constructors are obsolete and should not be used. You have to use the static function "Create" to create a new instance of the HttpWebRequest class:
HttpWebRequest myReq =
(HttpWebRequest)WebRequest.Create("http://www.contoso.com/");
To solve your issue, use the HttpClient class instead. This class has a parameterless constructor.
I have a Azure Function with 2 triggers:
I’m registering IService in my Startup like so:
I need a different configuration in the Service class depending on which trigger that is calling DoWork()? How can I achieve this using DI?
public class Service : IService
{
public Service(/*Configuration to be injected depends on calling trigger */)
{ }
public void DoWork()
{ }
}
Configuration extract:
Thankyou user1672994. Posting your suggestion as an answer so that it will be helpful for other community members who face similar kind of issues.
Below is the example code to implement todo work items where this will be helpful in resolving your issue.
using AZV3CleanArchitecture.Models;
using AZV3CleanArchitecture.Options;
using AZV3CleanArchitecture.Providers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
namespace AZV3CleanArchitecture.Services
{
public class ToDoItemsService : IToDoItemsService
{
private readonly HttpClient httpClient;
private readonly ToDoItemsServiceOptions toDoItemsServiceOptions;
private readonly ILogger<ToDoItemsService> logger;
public ToDoItemsService(HttpClient httpClient, IOptions<ToDoItemsServiceOptions> toDoItemsServiceOptions, ILogger<ToDoItemsService> logger)
{
this.httpClient = httpClient;
this.toDoItemsServiceOptions = toDoItemsServiceOptions.Value;
this.logger = logger;
}
public async Task<ToDoItem> GetToDoItem(int id)
{
logger.LogInformation($"Retrieving item: {{{Constants.TodoItemId}}}", id);
var getUrl = $"{this.toDoItemsServiceOptions.BaseUrl.TrimEnd('/')}/todos/{id}";
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, getUrl))
{
using (var response = await this.httpClient.SendAsync(requestMessage))
{
string responseString = await response.Content.ReadAsStringAsync();
logger.LogWarning($"Retrieved item: {{{Constants.TodoItemId}}}. Logged as warning for demo.", id);
return JsonConvert.DeserializeObject<ToDoItem>(responseString);
}
}
}
public async Task<IEnumerable<ToDoItem>> GetAllToDoItems(int id)
{
logger.LogInformation($"Retrieving all todo items");
var getUrl = $"{this.toDoItemsServiceOptions.BaseUrl.TrimEnd('/')}/todos";
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, getUrl))
{
using (var response = await this.httpClient.SendAsync(requestMessage))
{
string responseString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IEnumerable<ToDoItem>>(responseString);
}
}
}
public async Task<ToDoItem> CreateToDoItem(ToDoItem toDoItem)
{
// call service and return the output
return await Task.FromResult(new ToDoItem() { Id = 1, UserId = 1, Title = "Some Dummy Title", Completed = true });
}
public Task<ToDoItem> UpdateToDoItem(ToDoItem toDoItem)
{
throw new System.NotImplementedException();
}
}
}
for further information check the ToDoItemServices link.
Background: I am using ASP.NET Core 3.1, and integration testing a REST service that requires cookie authentication.
Candidate solution below.
Note:
The reason I use a vanilla Host instead of TestServer is because of the cookie requirement. When using TestServer, it provides an HttpClient for you, but the client does not pass cookies back to the server.
I also attempted to use a custom HttpClient with TestServer. That consistently generated a System.Net.Sockets.SocketException (No connection could be made because the target machine actively refused it.)
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi; // Contains my Startup.cs
namespace WebApiTest
{
[TestClass]
public class UserTest
{
static IHost HttpHost;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
HttpHost = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build();
await HttpHost.StartAsync();
}
[ClassCleanup]
public static async Task ClassCleanup()
{
await HttpHost.StopAsync();
}
public static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = null;
if (content != null)
{
httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
}
return httpContent;
}
public static HttpClient GetCookieHttpClient()
{
SocketsHttpHandler handler = new SocketsHttpHandler
{
AllowAutoRedirect = false,
CookieContainer = new CookieContainer(),
UseCookies = true
};
return new HttpClient(handler);
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (HttpClient client = GetCookieHttpClient())
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.PostAsync("http://localhost:5000/api/auth/login", GetHttpContent(credentials));
response = await client.GetAsync(String.Format("http://localhost:5000/api/users/{0}", credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}
HttpClient is a thin-client; it doesn't do anything unless you explicitly tell it to. In other words, it will never send the cookie for you; you must add a Cookie header to the request with the cookie value for each request. The test server "client" is just an HttpClient instance set up to proxy requests to the test server. You should use the test server, as prescribed, along with its client, and then add the Cookie header the requests you make with that.
Solutions based on Chris Pratt's suggestions
After some further digging, Microsoft provides a solution for this (WebApplicationFactory):
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi;
namespace WebApiTest
{
[TestClass]
public class Class2
{
static WebApplicationFactory<Startup> Factory;
static WebApplicationFactoryClientOptions ClientOptions;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
Factory = new WebApplicationFactory<Startup>();
ClientOptions = new WebApplicationFactoryClientOptions();
ClientOptions.AllowAutoRedirect = false;
ClientOptions.HandleCookies = true;
ClientOptions.BaseAddress = new Uri("http://localhost:5000");
}
public static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = null;
if (content != null)
{
httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
}
return httpContent;
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (HttpClient client = Factory.CreateClient(ClientOptions))
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.PostAsync("http://localhost:5000/api/auth/login", GetHttpContent(credentials));
response = await client.GetAsync(String.Format("http://localhost:5000/api/users/{0}", credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}
In case you want to stick with TestServer, here is a manual Cookie-passing implementation:
using Microsoft.AspNetCore.TestHost;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi;
namespace WebApiTest
{
public class CookieHttpClient : IDisposable
{
private static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
return httpContent;
}
private static IEnumerable<string> GetCookieStrings(CookieCollection collection)
{
List<string> output = new List<string>(collection.Count);
foreach (Cookie cookie in collection)
{
output.Add(cookie.Name + "=" + cookie.Value);
}
return output;
}
private HttpClient client;
private CookieContainer container;
public CookieHttpClient(HttpClient client)
{
this.client = client;
this.container = new CookieContainer();
}
public async Task<HttpResponseMessage> SendAsync(HttpMethod method, Uri uri)
{
return await this.SendAsync(method, uri, null);
}
public async Task<HttpResponseMessage> SendAsync(HttpMethod method, Uri uri, object data)
{
HttpRequestMessage request = new HttpRequestMessage(method, uri);
// Add data
if (data != null)
{
request.Content = GetHttpContent(data);
}
// Add cookies
CookieCollection collection = this.container.GetCookies(uri);
if (collection.Count > 0)
{
request.Headers.Add("Cookie", GetCookieStrings(collection));
}
HttpResponseMessage response = await this.client.SendAsync(request);
// Remember cookies before returning
if (response.Headers.Contains("Set-Cookie"))
{
foreach (string s in response.Headers.GetValues("Set-Cookie"))
{
this.container.SetCookies(uri, s);
}
}
return response;
}
public void Dispose()
{
this.client.Dispose();
}
}
[TestClass]
public class Class1
{
static TestServer TestServer;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
IWebHostBuilder builder = new WebHostBuilder()
.UseStartup<Startup>();
TestServer = new TestServer(builder);
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (CookieHttpClient client = new CookieHttpClient(TestServer.CreateClient()))
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.SendAsync(HttpMethod.Post, new Uri("http://localhost:5000/api/auth/login"), credentials);
response = await client.SendAsync(HttpMethod.Get, new Uri("http://localhost:5000/api/users/" + credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}
Problem:
I need to render a Razor Page partial to a string.
Why I want this:
I want to create a controller action that responds with JSON containing a partial view and other optional parameters.
Attempts:
I am familiar with the following example that renders a View to a string: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs
However, it is not compatible with Pages, as it only searches in the Views directory, so even if I give it an absolute path to the partial it tries to locate my _Layout.cshtml (which it shouldn't even do!) and fails to find it.
I have tried to modify it so it renders pages, but I end up getting a NullReferenceException for ViewData in my partial when attempting to render it. I suspect it has to do with NullView, but I have no idea what to put there instead (the constructor for RazorView requires many objects that I don't know how to get correctly).
The code:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0: https://www.apache.org/licenses/LICENSE-2.0
// Modified by OronDF343: Uses pages instead of views.
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Routing;
namespace TestAspNetCore.Services
{
public class RazorPageToStringRenderer
{
private readonly IRazorViewEngine _viewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
public RazorPageToStringRenderer(
IRazorViewEngine viewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
_viewEngine = viewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
}
public async Task<string> RenderPageToStringAsync<TModel>(string viewName, TModel model)
{
var actionContext = GetActionContext();
var page = FindPage(actionContext, viewName);
using (var output = new StringWriter())
{
var viewContext = new ViewContext(actionContext,
new NullView(),
new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(),
new ModelStateDictionary())
{
Model = model
},
new TempDataDictionary(actionContext.HttpContext,
_tempDataProvider),
output,
new HtmlHelperOptions());
page.ViewContext = viewContext;
await page.ExecuteAsync();
return output.ToString();
}
}
private IRazorPage FindPage(ActionContext actionContext, string pageName)
{
var getPageResult = _viewEngine.GetPage(executingFilePath: null, pagePath: pageName);
if (getPageResult.Page != null)
{
return getPageResult.Page;
}
var findPageResult = _viewEngine.FindPage(actionContext, pageName);
if (findPageResult.Page != null)
{
return findPageResult.Page;
}
var searchedLocations = getPageResult.SearchedLocations.Concat(findPageResult.SearchedLocations);
var errorMessage = string.Join(
Environment.NewLine,
new[] { $"Unable to find page '{pageName}'. The following locations were searched:" }.Concat(searchedLocations));
throw new InvalidOperationException(errorMessage);
}
private ActionContext GetActionContext()
{
var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
}
}
}
This is how I did it.
As always register the Service in Startup.cs
services.AddScoped<IViewRenderService, ViewRenderService>();
The Service is defined as follows:
public interface IViewRenderService
{
Task<string> RenderToStringAsync<T>(string viewName, T model) where T : PageModel;
}
public class ViewRenderService : IViewRenderService
{
private readonly IRazorViewEngine _razorViewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
private readonly IHttpContextAccessor _httpContext;
private readonly IActionContextAccessor _actionContext;
private readonly IRazorPageActivator _activator;
public ViewRenderService(IRazorViewEngine razorViewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider,
IHttpContextAccessor httpContext,
IRazorPageActivator activator,
IActionContextAccessor actionContext)
{
_razorViewEngine = razorViewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
_httpContext = httpContext;
_actionContext = actionContext;
_activator = activator;
}
public async Task<string> RenderToStringAsync<T>(string pageName, T model) where T : PageModel
{
var actionContext =
new ActionContext(
_httpContext.HttpContext,
_httpContext.HttpContext.GetRouteData(),
_actionContext.ActionContext.ActionDescriptor
);
using (var sw = new StringWriter())
{
var result = _razorViewEngine.FindPage(actionContext, pageName);
if (result.Page == null)
{
throw new ArgumentNullException($"The page {pageName} cannot be found.");
}
var view = new RazorView(_razorViewEngine,
_activator,
new List<IRazorPage>(),
result.Page,
HtmlEncoder.Default,
new DiagnosticListener("ViewRenderService"));
var viewContext = new ViewContext(
actionContext,
view,
new ViewDataDictionary<T>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = model
},
new TempDataDictionary(
_httpContext.HttpContext,
_tempDataProvider
),
sw,
new HtmlHelperOptions()
);
var page = ((Page)result.Page);
page.PageContext = new Microsoft.AspNetCore.Mvc.RazorPages.PageContext
{
ViewData = viewContext.ViewData
};
page.ViewContext = viewContext;
_activator.Activate(page, viewContext);
await page.ExecuteAsync();
return sw.ToString();
}
}
}
I call it like this
emailView.Body = await this._viewRenderService.RenderToStringAsync("Email/ConfirmAccount", new Email.ConfirmAccountModel
{
EmailView = emailView,
});
"Email/ConfirmAccount" is the path to my Razor page (Under pages). "ConfirmAccountModel" is my page model for that page.
ViewData is null because the ViewData for the Page is set when the PageContext is set, so if this is not set ViewData is null.
I also found that I had to call
_activator.Activate(page, viewContext);
For it all to work. This is not fully tested yet so may not work for all scenarios but should help you get started.
If like me you don't get GetRouteData() from _httpContext.HttpContext and _actionContext is null, you can create an extension:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace Utils
{
public static class PageExtensions
{
public static async Task<string> RenderViewAsync(this PageModel pageModel, string pageName)
{
var actionContext = new ActionContext(
pageModel.HttpContext,
pageModel.RouteData,
pageModel.PageContext.ActionDescriptor
);
using (var sw = new StringWriter())
{
IRazorViewEngine _razorViewEngine = pageModel.HttpContext.RequestServices.GetService(typeof(IRazorViewEngine)) as IRazorViewEngine;
IRazorPageActivator _activator = pageModel.HttpContext.RequestServices.GetService(typeof(IRazorPageActivator)) as IRazorPageActivator;
var result = _razorViewEngine.FindPage(actionContext, pageName);
if (result.Page == null)
{
throw new ArgumentNullException($"The page {pageName} cannot be found.");
}
var page = result.Page;
var view = new RazorView(_razorViewEngine,
_activator,
new List<IRazorPage>(),
page,
HtmlEncoder.Default,
new DiagnosticListener("ViewRenderService"));
var viewContext = new ViewContext(
actionContext,
view,
pageModel.ViewData,
pageModel.TempData,
sw,
new HtmlHelperOptions()
);
var pageNormal = ((Page)result.Page);
pageNormal.PageContext = pageModel.PageContext;
pageNormal.ViewContext = viewContext;
_activator.Activate(pageNormal, viewContext);
await page.ExecuteAsync();
return sw.ToString();
}
}
}
}
Note: this code only render the page being called and omit the layout.
You just have to call it from your PageModel like this:
var s = this.RenderViewAsync("sendEmail").Result;
"sendEmail" is the name of your PageModel view and the path is /Pages/sendEmail.cshtml
Here is the route I have gone down. Very simple and works a treat...
using System;
using System.IO;
using System.Net;
namespace gMIS.Rendering
{
public static class RazorPage
{
public static string RenderToString(string url)
{
try
{
//Grab page
WebRequest request = WebRequest.Create(url);
WebResponse response = request.GetResponse();
Stream data = response.GetResponseStream();
string html = String.Empty;
using (StreamReader sr = new StreamReader(data))
{
html = sr.ReadToEnd();
}
return html;
}
catch (Exception err)
{
return {Handle as you see fit};
}
}
}
}
Called as such....
var msg = RazorPage.RenderToString(url);
Example:
var pathToRazorPageFolder = request.PathToRazorPageFolder();
var msg = RazorPage.RenderToString($"{pathToRazorPageFolder}/Task_Summary?userGuid={userGuid}&taskId={task.Task_ID}&includelink=true&linkuserGuid={linkUserGuid}");
Above example uses this extension I created to help get the base path of my app.
namespace Microsoft.AspNetCore.Http
{
public static class RequestExtension
{
public static string PathToRazorPageFolder(this HttpRequest request)
{
if (request != null) {
var requestPath = request.Path.ToString();
var returnPathToFolder = request.Scheme + "://" + request.Host + requestPath.Substring(0, requestPath.LastIndexOf("/")); ;
return returnPathToFolder;
} else
{
return "HttpRequest was null";
}
}
}
}
I know that this doesn't use Dependency Injection, but man it's simple. And it just works. And works with any page no matter how it is hosted. Be that page be within or even outside your application.
I had the same problem.
I looked into the RazorViewEngine source code and found out that the page
is searched using the "page" route data:
var routeData = new RouteData();
routeData.Values.Add("page", "/Folder/MyPage");
It's working for me with the full path "/Folder/MyPage" in the routeData, and the page name "MyPage" in the GetPage call.
I would like to mockup the RestClient class for test purposes
public class DataServices : IDataServices
{
private readonly IRestClient _restClient;
public DataServices(IRestClient restClient)
{
_restClient = restClient;
}
public async Task<User> GetUserByUserName(string userName)
{
User user = null;
// create a new request
var restRequest = new RestRequest("User", Method.GET);
// create REST parameters
restRequest.AddParameter("userName", userName, ParameterType.QueryString);
// execute the REST request
var restResponse = await _restClient.Execute<User>(restRequest);
if (restResponse.StatusCode.Equals(HttpStatusCode.OK))
{
user = restResponse.Data;
}
return user;
}
}
My test class :
[TestClass]
public class DataServicesTest
{
public static IRestClient MockRestClient<T>(HttpStatusCode httpStatusCode, string json)
{
var mockIRestClient = new Mock<IRestClient>();
mockIRestClient.Setup(x => x.Execute<T>(It.IsAny<IRestRequest>()))
.Returns(new RestResponse<T>
{
Data = JsonConvert.DeserializeObject<T>(json),
StatusCode = httpStatusCode
});
return mockIRestClient.Object;
}
[TestMethod]
public async void GetUserByUserName()
{
var dataServices = new DataServices(MockRestClient<User>(HttpStatusCode.OK, "my json code"));
var user = await dataServices.GetUserByUserName("User1");
Assert.AreEqual("User1", user.Username);
}
}
But I can't instantiate the RestResponse object, I've the following error:
.Returns(new RestResponse<T>
{
Data = JsonConvert.DeserializeObject<T>(json),
StatusCode = httpStatusCode
});
Cannot access protected internal constructor 'RestResponse' here.
How can I workaround this ? I'm using the FubarCoder.RestSharp nuget package on a Xamarin portable Library.
Mock IRestResponse<T> and return that
public static IRestClient MockRestClient<T>(HttpStatusCode httpStatusCode, string json)
where T : new() {
var data = JsonConvert.DeserializeObject<T>(json)
var response = new Mock<IRestResponse<T>>();
response.Setup(_ => _.StatusCode).Returns(httpStatusCode);
response.Setup(_ => _.Data).Returns(data);
var mockIRestClient = new Mock<IRestClient>();
mockIRestClient
.Setup(x => x.Execute<T>(It.IsAny<IRestRequest>()))
.ReturnsAsync(response.Object);
return mockIRestClient.Object;
}
The test should also be updated to be async as well
[TestMethod]
public async Task GetUserByUserName() {
//Arrange
var client = MockRestClient<User>(HttpStatusCode.OK, "my json code");
var dataServices = new DataServices(client);
//Act
var user = await dataServices.GetUserByUserName("User1");
//Assert
Assert.AreEqual("User1", user.Username);
}
I didn't find any great answers so I ended up writing a helper library. I published it to NuGet - MoqRestSharp.Helpers. This project is aimed to help unit test RestSharp as it extends Mock so this helped me test my RestSharp requests and response error handling.
It uses Moq
NuGet Link
Repository Link - Examples are in the project too
Feedback is always welcome!
Complete solution
using Moq;
using Newtonsoft.Json;
using NUnit.Framework;
using RestSharp;
using System.Net;
namespace RestMockTest
{
public class Tests
{
[Test]
public void Test1()
{
var client = MockRestClient<User>(HttpStatusCode.OK, "{\"Name\":\"User1\"}");
var restRequest = new RestRequest("api/item/", Method.POST);
var restResponse = client.Execute<User>(restRequest);
var user = restResponse.Data;
Assert.AreEqual("User1", user.Name);
}
public static IRestClient MockRestClient<T>(HttpStatusCode httpStatusCode, string json)
where T : new()
{
var data = JsonConvert.DeserializeObject<T>(json);
var response = new Mock<IRestResponse<T>>();
response.Setup(_ => _.StatusCode).Returns(httpStatusCode);
response.Setup(_ => _.Data).Returns(data);
var mockIRestClient = new Mock<IRestClient>();
mockIRestClient
.Setup(x => x.Execute<T>(It.IsAny<IRestRequest>()))
.Returns(response.Object);
return mockIRestClient.Object;
}
}
public class User
{
public string Name { get; set; }
}
}