ASP.NET Core Integration Test for controller action - asp.net-core

Microsoft documentation (https://learn.microsoft.com/en-us/aspnet/core/testing/integration-testing) explain how to implement an integration test using the TestServer class. It is easy in case we are using WEB API because we get the serialized model as response from the action.
But in case I want to test a Controller action returning an HTML View containing some data, how can I evaluate that the page content is what I expect (avoiding to scan the HTML page contents) ?

One option is to use Automated UI Testing using something like Selenium

In order to append this JSON serialized view model to your page, I implemented the following filter:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Newtonsoft.Json;
using Ticketino.Web.Components.Extensions.Request;
using Ticketino.Web.OnlineShop.Serializations;
using Ticketino.Web.OnlineShop.ViewModels.Base;
namespace Ticketino.Web.OnlineShop.Filters
{
/// <summary>
/// This is a filter used only for integration tests.
/// It format the ViewModel as jSon and appends it to the end of HMTL page, so that it can be deserialized from the test in order to check its values.
/// </summary>
/// <seealso cref="Microsoft.AspNetCore.Mvc.Filters.ResultFilterAttribute" />
[AttributeUsage(AttributeTargets.Method)]
public class IntegrationTestFilterAttribute : ResultFilterAttribute
{
public const string StartViewModelContainer = "<script type=\"model/json\">";
public const string EndViewModelContainer = "</script>";
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if (!filterContext.ModelState.IsValid)
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult?.Model is BaseViewModel)
{
var errors = IntegrationTestFilterAttribute.GetModelErrors(filterContext.ModelState);
((BaseViewModel)viewResult.Model).ValidationErrors = errors;
}
}
base.OnResultExecuting(filterContext);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult?.Model != null)
{
var jsonViewModel = string.Concat(
IntegrationTestFilterAttribute.StartViewModelContainer,
JsonConvert.SerializeObject(viewResult.Model, Formatting.None, CommonJsonSerializerSettings.Settings()),
IntegrationTestFilterAttribute.EndViewModelContainer);
filterContext.HttpContext.Response.WriteAsync(jsonViewModel);
}
}
base.OnResultExecuted(filterContext);
}
#region Private methods
private static IDictionary<string, string> GetModelErrors(ModelStateDictionary errDictionary)
{
var errors = new Dictionary<string, string>();
//get all entries from the ModelStateDictionary that have any errors and add them to our Dictionary
errDictionary.Where(k => k.Value.Errors.Count > 0).ForEach(i =>
{
foreach (var errorMessage in i.Value.Errors.Select(e => e.ErrorMessage))
{
errors.Add(i.Key, errorMessage);
}
});
return errors;
}
#endregion
}
}
Then, in ConfigureServices(IServiceCollection serviceCollection) method inject it when you run integration test as show:
// Filter to append json serialized view model to buttom html response page, in order to eveluate from integration test class
if (_hostingEnvironment.IsIntegrationTest())
{
mvcBuilder.AddMvcOptions(opt => { opt.Filters.Add(new IntegrationTestFilterAttribute()); });
}

Related

Why does 'InputField' not contain a definition for 'text'?

I'm currently working on a Unity project for a college assignment, and I'm currently trying to connect a login/registration through PlayFab into a teammate's main menu for the game.
I've connected the PlayFabManager.cs script to the Input Fields for the email and password in the Unity editor, and something about my InputFields.cs file is preventing me from making any more progress.
I had to change the passwordInput and emailInput variables to TMP_InputField variables to achieve this, but now I am getting a compilation error in my project that says the following:
Assets\Scripts\InputField.cs(13,24): error CS1061: 'InputField' does not contain a definition for 'text' and no accessible extension method 'text' accepting a first argument of type 'InputField' could be found (are you missing a using directive or an assembly reference?)
Most places I look have people not including the "using UnityEngine.UI;" header at the top of the file, but that's included in my InputField.cs file.
Here's the code for my InputField.cs file:
using UnityEngine;
using System.Collections;
using UnityEngine.UI; // Required when Using UI elements.
public class InputField : MonoBehaviour
{
public InputField mainInputField;
public void Start()
{
mainInputField.text = "Enter text here...";
}
}
Here's the code for my PlayFabManager.cs file:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PlayFab;
using PlayFab.ClientModels;
using Newtonsoft.Json;
using UnityEngine.UI;
using TMPro; // Needed for login input fields
public class PlayFabManager : MonoBehaviour
{
[Header("UI)")]
public Text messageText;
public TMP_InputField emailInput;
public TMP_InputField passwordInput;
// Register/Login/ResetPassword
public void RegisterButton() {
var request = new RegisterPlayFabUserRequest {
Email = emailInput.text,
Password = passwordInput.text,
RequireBothUsernameAndEmail = false
};
PlayFabClientAPI.RegisterPlayFabUser(request, OnRegisterSuccess, OnError);
}
void OnRegisterSuccess(RegisterPlayFabUserResult result) {
messageText.text = "Registered and Logged in";
}
public void LoginButton() {
}
// Start is called before the first frame update
void Start() {
Login();
}
// Update is called once per frame
void Login() {
var request = new LoginWithCustomIDRequest {
CustomId = SystemInfo.deviceUniqueIdentifier,
CreateAccount = true
};
PlayFabClientAPI.LoginWithCustomID(request, OnSuccess, OnError);
}
void OnSuccess(LoginResult result) {
Debug.Log("Successful login/account create.");
}
void OnError(PlayFabError error) {
Debug.Log("Error while loggin in/creating account.");
Debug.Log(error.GenerateErrorReport());
}
}
I would just remove the InputField.cs class as it fixes my errors, but it changes the functionality of the following code that my teammate has contributed:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class MenuControl : MonoBehaviour
{
public string newGameLevel;
public void NewUser() {
SceneManager.LoadScene(newGameLevel);
}
public void ExitButton() {
Application.Quit();
}
}
Any help would be much appreciated!
Wanted to provide the solution in case this happens to anyone in the future:
I solved the problem by changing the
public InputField mainInputField;
into an input variable that could receive the TMP_Imput like so: public TMPro.TMP_InputField mainInputField;

How to use ReadAsStringAsync in asp.net core MVC controller?

How to use ReadAsStringAsync in asp.net core MVC controller?
The Microsoft.AspNetCore.Mvc.Request does not have Content property. Is there an alternative to this? Thank you!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using AuthLibrary;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web;
using System.Web.Http;
using System.Threading.Tasks;
[Microsoft.AspNetCore.Mvc.Route("TestAPI")]
public class TestController : Controller
{
[Microsoft.AspNetCore.Mvc.HttpPost]
[AllowAnonymous]
[Microsoft.AspNetCore.Mvc.Route("Start")]
public async Task<HttpResponseMessage> Start()
{
string req = await this.Request.Content.ReadAsStringAsync();
////
}
}
For Asp.Net Core MVC, you could access the request content with request.Body.
Here is an extension:
public static class HttpRequestExtensions
{
/// <summary>
/// Retrieve the raw body as a string from the Request.Body stream
/// </summary>
/// <param name="request">Request instance to apply to</param>
/// <param name="encoding">Optional - Encoding, defaults to UTF8</param>
/// <returns></returns>
public static async Task<string> GetRawBodyStringAsync(this HttpRequest request, Encoding encoding = null)
{
if (encoding == null)
encoding = Encoding.UTF8;
using (StreamReader reader = new StreamReader(request.Body, encoding))
return await reader.ReadToEndAsync();
}
/// <summary>
/// Retrieves the raw body as a byte array from the Request.Body stream
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static async Task<byte[]> GetRawBodyBytesAsync(this HttpRequest request)
{
using (var ms = new MemoryStream(2048))
{
await request.Body.CopyToAsync(ms);
return ms.ToArray();
}
}
}
Use:
public async Task<string> ReadStringDataManual()
{
return await Request.GetRawBodyStringAsync();
}
Reference:Accepting Raw Request Body Content in ASP.NET Core API Controllers
You hope you can use .ReadAsStringAsync() on the current MVC request because perhaps you've seen something like this?
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Threading.Tasks;
namespace DL.SO.UI.Web.Controllers
{
public class DashboardController : Controller
{
// In order to be able to inject the factory, you need to register in Startup.cs
// services.AddHttpClient()
// .AddRouting(...)
// .AddMvc(...);
private readonly IHttpClientFactory _httpClientFactory;
public DashboardController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<IActionResult> Index()
{
var client = _httpClientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://www.google.com");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string bodyContent = await response.Content.ReadAsStringAsync();
}
return View();
}
}
}
This is how you use HttpClient to fetch external data/resources in an app. .ReadAsStringAsync() is off an HttpContent, which is the property of either HttpRequestMessage or HttpResponseMessage. Both HttpRequestMessage and HttpResponseMessage are in System.Net.Http namespace.
But now you're in the app itself! Things work a little bit differently. We don't have a response for the request yet (because we haven't done return View();). Hence I assume the content you want to look at is the content of the request coming in?
GET request's content
When a GET request comes in, MVC will automatically bind request's query strings to action method parameters in the controller. They're also available in the Query property off the current Request object:
public IActionResult Index(int page = 1, int size = 15)
{
foreach (var param in Request.Query)
{
...
}
return View();
}
POST request's content
When a POST request comes in, Request.Body might not always have the data you're looking for. It depends on the content type of the POST request.
By default when you're submitting a form, the content type of the request is form-data. MVC then will bind the inputs to your view model as the action parameter:
[HttpPost]
public async Task<IActionResult> Close(CloseReservationViewModel model)
{
Request.Form // contains all the inputs, name/value pairs
Request.Body // will be empty!
...
}
If you use jQuery to fire POST requests without specifying the contentType, it defaults to x-www-form-urlencoded:
#section scripts {
<script type="text/javascript">
$(function() {
$.ajax({
url: '#Url.Action("test", "dashboard", new { area = "" })',
data: {
name: 'David Liang',
location: 'Portland Oregon'
},
method: 'POST'
}).done(function (response) {
console.info(response);
});
});
</script>
}
[HttpPost]
public async Task<IActionResult> Test()
{
string body;
using (var reader = new StreamReader(Request.Body))
{
body = await reader.ReadToEndAsync();
}
return Json(body);
}
Conclusion
If you want to use HttpClient to call external services inside your MVC app, you can utilize IHttpClientFactory, HttpClient from System.Net.Http and get a HttpContent from either the request or response without too much trouble. Then you can do ReadAsStringAsync() off it.
If you want to peek on the request data sent from the client to your MVC app, MVC has already done so much to help you bind the data using model binding. You can also read request's body for POST requests with a StreamReader. Just pay attention that depends on the content type, Request.Body might not have what you expect.

DataAnnotationsModelValidatorProvider.RegisterAdapter in ASP.Net Core MVC

In ASP.Net MVC 5, custom data annotation validator can be implemented by inheriting DataAnnotationsModelValidator and registering using DataAnnotationsModelValidatorProvider.RegisterAdapter(...). In ASP.Net Core MVC, how can I achieve this?
I found similar question at ASP.net core MVC 6 Data Annotations separation of concerns, but can anyone show me simple example code?
It seems to me ASP.NET Core MVC does not have support for DataAnnotationsModelValidatorProvider.RegisterAdapter anymore. The solution I discovered is as follows:
Suppose I want to change the Validator for RequiredAttribute to my own validator adaptor (MyRequiredAttributeAdaptor), Change the default error message of EmailAddressAttribute, and change the Localized Error Message Source for 'CompareAttribute' to my own message.
1- Create a custom ValidationAttributeAdapterProvider
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.Extensions.Localization;
using System.ComponentModel.DataAnnotations;
public class CustomValidationAttributeAdapterProvider
: ValidationAttributeAdapterProvider, IValidationAttributeAdapterProvider
{
public CustomValidationAttributeAdapterProvider() { }
IAttributeAdapter IValidationAttributeAdapterProvider.GetAttributeAdapter(
ValidationAttribute attribute,
IStringLocalizer stringLocalizer)
{
IAttributeAdapter adapter;
if (attribute is RequiredAttribute)
{
adapter = new MyRequiredAttributeAdaptor((RequiredAttribute) attribute, stringLocalizer);
}
else if (attribute is EmailAddressAttribute)
{
attribute.ErrorMessage = "Invalid Email Address.";
adapter = base.GetAttributeAdapter(attribute, stringLocalizer);
}
else if (attribute is CompareAttribute)
{
attribute.ErrorMessageResourceName = "InvalidCompare";
attribute.ErrorMessageResourceType = typeof(Resources.ValidationMessages);
var theNewattribute = attribute as CompareAttribute;
adapter = new CompareAttributeAdapter(theNewattribute, stringLocalizer);
}
else
{
adapter = base.GetAttributeAdapter(attribute, stringLocalizer);
}
return adapter;
}
}
2- Add the CustomValidationAttributeAdapterProvider to start up:
Add the following line to public void ConfigureServices(IServiceCollection services) in Startup.cs:
services.AddSingleton <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider> ();
Here is MyRequiredAttributeAdaptor adaptor:
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Localization;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
public class MyRequiredAttributeAdaptor : AttributeAdapterBase<RequiredAttribute>
{
public MyRequiredAttributeAdaptor(RequiredAttribute attribute, IStringLocalizer stringLocalizer)
: base(attribute, stringLocalizer)
{
}
public override void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-required", GetErrorMessage(context));
}
/// <inheritdoc />
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
if (validationContext == null)
{
throw new ArgumentNullException(nameof(validationContext));
}
return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
}
}
References:
1- See the example of Microsoft: Entropy project: This is a great sample for diffrent features of .NET Core. In this question: see the MinLengthSixAttribute implementation in the Mvc.LocalizationSample.Web sample:
https://github.com/aspnet/Entropy/tree/dev/samples/Mvc.LocalizationSample.Web
2- In order to see how the attribute adapters works see asp.Microsoft.AspNetCore.Mvc.DataAnnotations on github:
https://github.com/aspnet/Mvc/tree/master/src/Microsoft.AspNetCore.Mvc.DataAnnotations
To define a custom validator by a annotation you can define your own class that derives from ValidationAttribute and override the IsValid method. There is no need to register this class explicitly.
In this example a custom validation attribute is used to accept only odd numbers as valid values.
public class MyModel
{
[OddNumber]
public int Number { get; set; }
}
public class OddNumberAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
try
{
var number = (int) value;
if (number % 2 == 1)
return ValidationResult.Success;
else
return new ValidationResult("Only odd numbers are valid.");
}
catch (Exception)
{
return new ValidationResult("Not a number.");
}
}
}
A second approach is that the Model class implements IValidatableObject. This is especially useful, if validation requires access to multiple members of the model class. Here is the second version of the odd number validator:
public class MyModel : IValidatableObject
{
public int Number { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Number % 2 == 0)
yield return new ValidationResult(
"Only odd numbers are valid.",
new [] {"Number"});
}
}
You can find more information about custom validation in https://docs.asp.net/en/latest/mvc/models/validation.html#custom-validation.

Sniff request in ActionFilter

One parameter in a Web API method is unexpectedly null, so I want to inspect the request. In support of this I wrote an ActionFilterAttribute and implemented the OnActionExecuting method. Attempting to retrieve Content as per the code below returns an empty string, but ContentLength says content is 345 bytes and content type is JSON (as expected).
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace Website.ActionFilters
{
public class ActionFilterSniffAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
Task<string> task = actionContext.Request.Content.ReadAsStringAsync();
while (task.Status != TaskStatus.RanToCompletion)
Thread.Sleep(10);
Debug.WriteLine(task.Result);
}
}
}
What is the correct way to get hold of the HTTP request string? Installing Fiddler on the server is not something I'm keen to do.
This mechanism worked for me and is a good explanation of what is occurring.
Web API action filter content can't be read
public override async void OnActionExecuting(HttpActionContext actionContext)
{
System.Net.Http.HttpRequestMessage request = actionContext.Request;
Stream reqStream = await request.Content.ReadAsStreamAsync();
if (reqStream.CanSeek)
{
reqStream.Position = 0;
}
//now try to read the content as string
string data = await request.Content.ReadAsStringAsync();
Debugger.Break();
}

How to add web API to an existing MVC Hottowel project

I have one Hottowel project created using it's template from Visual Studio. I want to add the Web API feature in that project. I have created a Web Api controller to the controller folder and tries to access like "http://localhost:53397/api/Values" But I get an error saying The resource cannot be found error.
My controller code looks like below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace MvcApplication8.Controllers
{
public class ValuesController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST api/<controller>
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
}
I have the cs file in APP_start folder called BreezeWebApiConfig.cs which contains the logic to map the route like below.
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "BreezeApi",
routeTemplate: "api/{controller}/{action}"
);
Let me know If I am missing any configuration setting for Web APi.
Try to decorate your ApiController like bellow :
[BreezeController]
public class NorthwindIBModelController : System.Web.Http.ApiController {
readonly EFContextProvider<NorthwindIBContext> ContextProvider =
new EFContextProvider<NorthwindIBContext>();
[HttpGet]
public String Metadata() {
return ContextProvider.Metadata();
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle) {
return ContextProvider.SaveChanges(saveBundle);
}
[HttpGet]
public IQueryable<Customer> Customers() {
return ContextProvider.Context.Customers;
}
For more information have a look to breeze documentation here.
Its seems like you are making a wrong Url Request. Look at your breeze route configuration for WebApi. You need to Pass like that http://localhost:53397/api/Values/Get as breeze is using Controller action based routing.
Hope this will help.