Using WebAPI in LINQPad? - asp.net-web-api

When I tried to use the Selfhosted WebAPI in LINQPad, I just kept getting the same error that a controller for the class didn't exist.
Do I have to create separate assemblies for the WebAPI (Controllers/Classes) and then reference them in my query?
Here's the code I'm using
#region namespaces
using AttributeRouting;
using AttributeRouting.Web.Http;
using AttributeRouting.Web.Http.SelfHost;
using System.Web.Http.SelfHost;
using System.Web.Http.Routing;
using System.Web.Http;
#endregion
public void Main()
{
var config = new HttpSelfHostConfiguration("http://192.168.0.196:8181/");
config.Routes.MapHttpAttributeRoutes(cfg =>
{
cfg.AddRoutesFromAssembly(Assembly.GetExecutingAssembly());
});
config.Routes.Cast<HttpRoute>().Dump();
AllObjects.Add(new UserQuery.PlayerObject { Type = 1, BaseAddress = "Hej" });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
using(HttpSelfHostServer server = new HttpSelfHostServer(config))
{
server.OpenAsync().Wait();
Console.WriteLine("Server open, press enter to quit");
Console.ReadLine();
server.CloseAsync();
}
}
public static List<PlayerObject> AllObjects = new List<PlayerObject>();
public class PlayerObject
{
public uint Type { get; set; }
public string BaseAddress { get; set; }
}
[RoutePrefix("players")]
public class PlayerObjectController : System.Web.Http.ApiController
{
[GET("allPlayers")]
public IEnumerable<PlayerObject> GetAllPlayerObjects()
{
var players = (from p in AllObjects
where p.Type == 1
select p);
return players.ToList();
}
}
This code works fine when in a separate Console Project in VS2012.
I started using AttributeRouting via NuGET when I didn't get the "normal" WebAPI-routing to work.
The error I got in the browser was: No HTTP resource was found that matches the request URI 'http://192.168.0.196:8181/players/allPlayers'.
Additional error: No type was found that matches the controller named 'PlayerObject'

Web API by default will ignore controllers that are not public, and LinqPad classes are nested public, we had similar problem in scriptcs
You have to add a custom controller resolver, which will bypass that limitation, and allow you to discover controller types from the executing assembly manually.
This was actually fixed already (now Web API controllers only need to be Visible not public), but that happened in September and the latest stable version of self host is from August.
So, add this:
public class ControllerResolver: DefaultHttpControllerTypeResolver {
public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver) {
var types = Assembly.GetExecutingAssembly().GetExportedTypes();
return types.Where(x => typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(x)).ToList();
}
}
And then register against your configuration, and you're done:
var conf = new HttpSelfHostConfiguration(new Uri(address));
conf.Services.Replace(typeof(IHttpControllerTypeResolver), new ControllerResolver());
Here is a full working example, I just tested against LinqPad. Note that you have to be running LinqPad as admin, otherwise you won't be able to listen at a port.
public class TestController: System.Web.Http.ApiController {
public string Get() {
return "Hello world!";
}
}
public class ControllerResolver: DefaultHttpControllerTypeResolver {
public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver) {
var types = Assembly.GetExecutingAssembly().GetExportedTypes();
return types.Where(x => typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(x)).ToList();
}
}
async Task Main() {
var address = "http://localhost:8080";
var conf = new HttpSelfHostConfiguration(new Uri(address));
conf.Services.Replace(typeof(IHttpControllerTypeResolver), new ControllerResolver());
conf.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var server = new HttpSelfHostServer(conf);
await server.OpenAsync();
// keep the query in the 'Running' state
Util.KeepRunning();
Util.Cleanup += async delegate {
// shut down the server when the query's execution is canceled
// (for example, the Cancel button is clicked)
await server.CloseAsync();
};
}

Related

Automatically switching views for AMP in ASP.NET MVC

I want to create and AMP version of my website in ASP.NET MVC using .NET Core 2.0. Previously I had done some work with DisplayModeProvider instances in tha past on .Net framework, but that does not seem to be an option in .NET Core.
What I want to be able to do is alter the view names to be index.amp.cshtml rather than index.cshtml when my URL starts iwth /amp. What's the best way to achieve this in .NET Core?
You can do something like this using IViewLocationExpander. As it happens, I was playing with this a few days ago so I have some code samples to hand. If you create something like this:
public class AmpViewLocationExpander : IViewLocationExpander
{
public void PopulateValues(ViewLocationExpanderContext context)
{
var contains = context.ActionContext.HttpContext.Request.Query.ContainsKey("amp");
context.Values.Add("AmpKey", contains.ToString());
var containsStem = context.ActionContext.HttpContext.Request.Path.StartsWithSegments("/amp");
context.Values.Add("AmpStem", containsStem.ToString());
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
if (!(context.ActionContext.ActionDescriptor is ControllerActionDescriptor descriptor)) { return viewLocations; }
if (context.ActionContext.HttpContext.Request.Query.ContainsKey("amp")
|| context.ActionContext.HttpContext.Request.Path.StartsWithSegments("/amp")
)
{
return viewLocations.Select(x => x.Replace("{0}", "{0}.amp"));
}
return viewLocations;
}
}
iViewLocationExpander can be found in Microsoft.AspNetCore.Mvc.Razor
Then in your Configure Services method in Startup.cs, add the following:
services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new AmpViewLocationExtender());
});
What this will do is update the view locations per request to insert .amp before .cshtml any time the URL either starts with /amp or there is a query string key of amp. If your AMP views don't exist, it might blow-up a little, I've not fully tested it, but it should get you started.
You can define this Middleware :
public class AmpMiddleware
{
private RequestDelegate _next;
public AmpMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
const string ampTag = "/amp";
var path = context.Request.Path;
if (path.HasValue)
{
var ampPos = path.Value.IndexOf(ampTag);
if (ampPos >= 0)
{
context.Request.Path = new PathString(path.Value.Remove(ampPos, ampTag.Length));
context.Items.Add("amp", "true");
}
}
return _next(context);
}
}
public static class BuilderExtensions
{
public static IApplicationBuilder UseAmpMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware<AmpMiddleware>();
}
}
And call it in Startup:
app.UseAmpMiddleware();
Then can check in page and simple set another layout or limit some code, in his way no need to create separate page for amp version:
#if (HttpContext.Items.ContainsKey("amp"))
{
<b>Request AMP Version</b>
}

ASP.net 5 Web API Post CreatedAtRoute always returns 500 Internal Server Error

The database works. It does actually insert the new record, but when I use CreatedAtRoute(), I always get a 500 back from the client. Why?
My controller's Get:
[Route("api/[controller]")]
public class IngredientController : Controller
{
private SimpleCookbookDbContext db { get; set; }
public IngredientController(SimpleCookbookDbContext context)
{
db = context;
}
// GET: api/values
[HttpGet]
public async Task<IEnumerable<Ingredient>> Get()
{
return await db.Ingredients.ToListAsync();
}
// GET api/values/5
[HttpGet("{id}", Name = "GetIngredient")]
public async Task<Ingredient> Get(int id)
{
return await db.Ingredients.SingleOrDefaultAsync(i => i.Id == id);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]Ingredient ingredient)
{
try
{
var res = await IM.CreateAsync(ingredient);
if (!res.Success)
{
return HttpBadRequest(res.Errors);
}
}
catch(Exception)
{
return new HttpStatusCodeResult((int)HttpStatusCode.InternalServerError);
}
return CreatedAtRoute("GetIngredient", new { controller="Ingredient", id = ingredient.Id });
}
}
I tried debugging this. Yes, it would return the HttpBadRequest if the ingredient I'm trying to insert already exists.
I tried putting a breakpoint inside the catch block, and I'm not getting there, so I assume there was no error from the database.
The record does get inserted to the database. I do get to the line return CreatedAtRoute(...); but I get a 500 back. (I set a breakpoint there, too).
Now, I'm using fiddler. My request is this:
POST /api/ingredient HTTP/1.1
Host: localhost:55303
Content-Type: application/json;charset=utf-8
{"id":0, "name": "rosemary", "description": "rosemary"}
I also removed the double quotes on the property names, and I still get the same 500.
I do have camel-casing resolved at the Startup:
services.AddMvc().AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
I think I showed all relevant code. If you need more, please let me know.
UPDATE
CreatedAtRoute has an overload taking in three parameters:
return CreatedAtRoute("GetIngredient", new { controller="Ingredient", id = ingredient.Id }, ingredient);
The last parameter is an object, which you could create dynamically or pass back your entire entity, depending on what you want to expose back.
It's strange how there's a 2-parameter variant that would result in a strange 500 response.

Instantiate new System.Web.Http.OData.Query.ODataQueryOptions in nunit test of ASP.NET Web API controller

I have an ASP.NET MVC4 Web API project with an ApiController-inheriting controller that accepts an ODataQueryOptions parameter as one of its inputs.
I am using NUnit and Moq to test the project, which allow me to setup canned responses from the relevant repository methods used by the ApiController. This works, as in:
[TestFixture]
public class ProjectControllerTests
{
[Test]
public async Task GetById()
{
var repo = new Mock<IManagementQuery>();
repo.Setup(a => a.GetProjectById(2)).Returns(Task.FromResult<Project>(new Project()
{
ProjectID = 2, ProjectName = "Test project", ProjectClient = 3
}));
var controller = new ProjectController(repo.Object);
var response = await controller.Get(2);
Assert.AreEqual(response.id, 2);
Assert.AreEqual(response.name, "Test project");
Assert.AreEqual(response.clientId, 3);
}
}
The challenge I have is that, to use this pattern, I need to pass in the relevant querystring parameters to the controller as well as the repository (this was actually my intent). However, in the case of ODataQueryOptions-accepting ApiController methods, even in the cases where I would like to use just the default parameters for ODataQueryOptions, I need to know how to instantiate one. This gets tricky:
ODataQueryOptions does not implement an interface, so I can't mock it directly.
The constructor requires an implementation of System.Web.Http.OData.ODataQueryContext, which requires an implementation of something implementing Microsoft.Data.Edm.IEdmModel, for which the documentation is scarce and Visual Studio 2012 Find References and View Call Hierarchy do not provide insight (what implements that interface?).
What do I need to do/Is there a better way of doing this?
Thanks.
Looks like someone else already answered this in the comments here, but it's not a complete solution for my use-case (see comment below):
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customers");
var opts = new ODataQueryOptions<Customer>(new ODataQueryContext(modelBuilder.GetEdmModel(),typeof(Customer)), request);
This is the solution I have been using in my NUnit tests to inject ODataQueryOptions
private static IEdmModel _model;
private static IEdmModel Model
{
get
{
if (_model == null)
{
var builder = new ODataConventionModelBuilder();
var baseType = typeof(MyDbContext);
var sets = baseType.GetProperties().Where(c => c.PropertyType.IsGenericType && c.PropertyType.GetGenericTypeDefinition() == typeof(IDbSet<>));
var entitySetMethod = builder.GetType().GetMethod("EntitySet");
foreach (var set in sets)
{
var genericMethod = entitySetMethod.MakeGenericMethod(set.PropertyType.GetGenericArguments());
genericMethod.Invoke(builder, new object[] { set.Name });
}
_model = builder.GetEdmModel();
}
return _model;
}
}
public static ODataQueryOptions<T> QueryOptions<T>(string query = null)
{
query = query ?? "";
var url = "http://localhost/Test?" + query;
var request = new HttpRequestMessage(HttpMethod.Get, url);
return new ODataQueryOptions<T>(new ODataQueryContext(Model, typeof(T)), request);
}

how to unit test controller when automapper is used?

here's my controller
[POST("signup")]
public virtual ActionResult Signup(UserRegisterViewModel user)
{
if (ModelState.IsValid)
{
var newUser = Mapper.Map<UserRegisterViewModel, User>(user);
var confirmation = _userService.AddUser(newUser);
if (confirmation.WasSuccessful)
return RedirectToAction(MVC.Home.Index());
else
ModelState.AddModelError("Email", confirmation.Message);
}
return View(user);
}
here's my unit test:
[Test]
public void Signup_Action_When_The_User_Model_Is_Valid_Returns_RedirectToRouteResult()
{
// Arrange
const string expectedRouteName = "~/Views/Home/Index.cshtml";
var registeredUser = new UserRegisterViewModel { Email = "newuser#test.com", Password = "123456789".Hash()};
var confirmation = new ActionConfirmation<User>
{
WasSuccessful = true,
Message = "",
Value = new User()
};
_userService.Setup(r => r.AddUser(new User())).Returns(confirmation);
_accountController = new AccountController(_userService.Object);
// Act
var result = _accountController.Signup(registeredUser) as RedirectToRouteResult;
// Assert
Assert.IsNotNull(result, "Should have returned a RedirectToRouteResult");
Assert.AreEqual(expectedRouteName, result.RouteName, "Route name should be {0}", expectedRouteName);
}
Unit test failed right here.
var result = _accountController.Signup(registeredUser) as RedirectToRouteResult;
when I debug my unit test, I got following error message: "Missing type map configuration or unsupported mapping."
I think its because configuration is in web project, not the unit test project. what should I do to fix it?
You need to have the mapper configured, so in your test class set up, not the per-test setup, call the code to set up the mappings. Note, you'll also probably need to modify your expectation for the user service call as the arguments won't match, i.e, they are different objects. Probably you want a test that checks if the properties of the object match those of the model being passed to the method.
You should really use an interface for the mapping engine so that you can mock it rather than using AutoMapper otherwise it is an integration test not a unit test.
AutoMapper has an interface called IMappingEngine that you can inject into your controller using your IoC container like below (this example is using StructureMap).
class MyRegistry : Registry
{
public MyRegistry()
{
For<IMyRepository>().Use<MyRepository>();
For<ILogger>().Use<Logger>();
Mapper.AddProfile(new AutoMapperProfile());
For<IMappingEngine>().Use(() => Mapper.Engine);
}
}
You will then be able to use dependency injection to inject AutoMapper's mapping engine into your controller, allowing you to reference your mappings like below:
[POST("signup")]
public virtual ActionResult Signup(UserRegisterViewModel user)
{
if (ModelState.IsValid)
{
var newUser = this.mappingEngine.Map<UserRegisterViewModel, User>(user);
var confirmation = _userService.AddUser(newUser);
if (confirmation.WasSuccessful)
return RedirectToAction(MVC.Home.Index());
else
ModelState.AddModelError("Email", confirmation.Message);
}
return View(user);
}
You can read more about this here: How to inject AutoMapper IMappingEngine with StructureMap
Probably it is cool to abstract mapping into MappingEngine.
Sometimes I use following approach to IOC Automapper
In IOC builder:
builder.RegisterInstance(AutoMapperConfiguration.GetAutoMapper()).As<IMapper>();
where GetAutoMapper is:
public class AutoMapperConfiguration
{
public static IMapper GetAutoMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<OrderModelMapperProfile>();
cfg.AddProfile<OtherModelMapperProfile>();
//etc;
});
var mapper = config.CreateMapper();
return mapper;
}
}
And finally in Controller ctor
public MyController(IMapper mapper)
{
_mapper = mapper;
}

Silverlight localized custom validation using DataAnnotations with RIA Services

I've implemented localized validation, client-side, using the DataAnnotations attributes successfully. Now, I want to implement custom validation running server-side using the CustomValidationAttribute but my problem is that I can't find a way to get the client-side culture while executing the validation.
Here's the setup for the custom validation method:
public static ValidationResult ValidateField( string fieldValue, ValidationContext validationContext )
{
#if !SILVERLIGHT
// Get the message from the ValidationResources resx.
return new ValidationResult( ValidationResources.Message, new string[]{ "Field" } );
#else
return ValidationResult.Success;
#endif
}
This code returns the message but from the culture that the server is currently set.
I also tried to set the attribute on the property this way with same result:
[CustomValidation( typeof( CustomValidation ), "ValidateField", ErrorMessageResourceName = "Message", ErrorMessageResourceType = typeof( ValidationResources ) )]
I also tried to expose a method on my DomainService to change the Culture on the ValidationResources resx but this seems to be changing the culture not only or the current connection but for all the connections.
Since the validation is ran by Ria Services and not something I am calling directly, how can I tell the validation method to use a specific culture?
I came across this thread and I was able to fix my issue and have the culture name pass to every request made by the DomainContext (client) to the server.
First, we need to create a custom IClientMessageInspector that will be responsible to set a parameter for the CurrentUICulture for every requests.
public class AppendLanguageMessageInspector : IClientMessageInspector
{
#region IClientMessageInspector Members
public void AfterReceiveReply( ref Message reply, object correlationState )
{
// Nothing to do
}
public object BeforeSendRequest( ref Message request, IClientChannel channel )
{
var property = request.Properties[ HttpRequestMessageProperty.Name ] as HttpRequestMessageProperty;
if( property != null )
{
property.Headers[ "CultureName" ] = Thread.CurrentThread.CurrentUICulture.Name;
}
return null;
}
#endregion // IClientMessageInspector Members
}
Next, we need to create a custom WebHttpBehavior that will inject our custom IClientMessageInspector.
public class AppendLanguageHttpBehavior : WebHttpBehavior
{
public override void ApplyClientBehavior( ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime )
{
clientRuntime.MessageInspectors.Add( _inspector );
}
private readonly AppendLanguageMessageInspector _inspector = new AppendLanguageMessageInspector();
}
Finally, we extend the client DomainContext.OnCreate method to add our custom WebHttpBehavior. NOTE: The namespace of the extended DomainContext class must be the same as the generated one...
public partial class DomainService1
{
partial void OnCreated()
{
var domainClient = this.DomainClient as WebDomainClient<IDomainService1Contract>;
if( domainClient != null )
{
domainClient.ChannelFactory.Endpoint.Behaviors.Add( DomainService1.AppendLanguageHttpBehavior );
}
}
private static readonly AppendLanguageHttpBehavior AppendLanguageHttpBehavior = new AppendLanguageHttpBehavior();
}
Now, on the server-side, when we want to get the language code we can simply access it like this:
var cultureName = System.Web.HttpContext.Current.Request.Headers[ "CultureName" ];
To enjoy even more of the DataAnnotation magic, we can even change the CurrentUICulture in the Initialize of the DomainService like this:
public override void Initialize( DomainServiceContext context )
{
var cultureName = System.Web.HttpContext.Current.Request.Headers[ "UICultureName" ];
Thread.CurrentThread.CurrentUICulture = new CultureInfo( cultureName );
base.Initialize( context );
}

Resources