Deploying Web API Controller to Production - asp.net-mvc-4

I have a ASP.NET MVC application in VS 2010. I added a new Web API Controller to my application. Here is the simple method I am trying to call:
public List<Article> Get()
{
using (var db = new HighOnCodingDbEntities())
{
var articles = (from a in db.Articles
select a).Take(10).ToList();
return articles;
}
}
Global.asax:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
When I call this method I get "Resource Not Found". I have published the application binary to the production and I believe that is all I need to do.
URL should be: http://www.highoncoding.com/api/articlesapi
ArticlesAPIController.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using HighOnCoding.Models;
namespace HighOnCoding.Controllers
{
public class ArticlesAPIController : ApiController
{
// GET api/<controller>
public List<Article> Get()
{
using (var db = new HighOnCodingDbEntities())
{
var articles = (from a in db.Articles
select a).Take(10).ToList();
return articles;
}
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST api/<controller>
public void Post(string value)
{
}
// PUT api/<controller>/5
public void Put(int id, string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
}
Works on local machine:

In production, ensure that the .NET Framework version for your IIS7 Application Pool for your website is set to .NET 4.0.xxx in integrated mode.

Related

Can't run tests for a controller doing Entity Framework Core operations in xUnit

I can't run tests for a controller doing Entity Framework Core operations in xUnit. I am using in-memory database and the error I am getting is:
**A test class may only define a single public constructor.**
The test class is:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyAppT.Controllers;
using MyAppT.Models;
using Xunit;
namespace TestingProject
{
public class TestRegistration
{
#region Seeding
protected TestRegistration(DbContextOptions<AppDbContext> contextOptions)
{
ContextOptions = contextOptions;
Seed();
}
protected DbContextOptions<AppDbContext> ContextOptions { get; }
private void Seed()
{
using (var context = new AppDbContext(ContextOptions))
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var one = new Register()
{
Name = "Test One",
Age = 40
};
var two = new Register()
{
Name = "Test Two",
Age = 50
};
var three = new Register()
{
Name = "Test Three",
Age = 60
};
context.AddRange(one, two, three);
context.SaveChanges();
}
}
#endregion
[Fact]
public void Test_Create_GET_ReturnsViewResultNullModel()
{
using (var context = new AppDbContext(ContextOptions))
{
// Arrange
var controller = new RegistrationController(context);
// Act
var result = controller.Create();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
Assert.Null(viewResult.ViewData.Model);
}
}
}
}
The controller that is doing EF core operations is:
using Microsoft.AspNetCore.Mvc;
using MyAppT.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MyAppT.Controllers
{
public class RegistrationController : Controller
{
private AppDbContext context;
public RegistrationController(AppDbContext appDbContext)
{
context = appDbContext;
}
public IActionResult Create()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Create(Register register)
{
if (ModelState.IsValid)
{
context.Add(register);
await context.SaveChangesAsync();
return RedirectToAction("Read");
}
else
return View();
}
}
}
The strange error while running the test shows up in Test Explorer - A test class may only define a single public constructor.
I could not find anything about it on stackoverflow. Please help in fixing it?
Your constructor needs to be parameterless for this to work, unless you're using some DI framework within your testing project, which is something that you generally shouldn't be doing.
Instead, try creating the DBContextOptions within the constructor and assigning it to your class variable. You can then use it when you seed the database, and when you test against it.
Try this instead. You will need to add the Microsoft.EntityFrameworkCore.InMemory package into your test project if you don't have this in there already.
public class TestRegistration
{
#region Seeding
public TestRegistration()
{
ContextOptions = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(databaseName: "Test")
.Options;
Seed();
}

Self-Host SignalR and Web Api in a single Console Application

In a single Console Application I must self-host both a SignalR Server and a Web Api.
I'm using this code
using System;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Hosting;
using Owin;
using Microsoft.Owin.Cors;
using System.Web.Http;
using System.Net.Http;
namespace SignalRSelfHost
{
class Program
{
static void Main(string[] args)
{
string url = "http://localhost:8080";
using (WebApp.Start(url))
{
Console.WriteLine("Server running on {0}", url);
//////////
// Create HttpCient and make a request to api/values
HttpClient client = new HttpClient();
var response = client.GetAsync(url + "/api/values").Result;
Console.WriteLine(response);
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
Console.ReadLine();
//////////
Console.ReadLine();
}
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
////////
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
app.UseWebApi(config);
/////////
app.MapSignalR();
}
}
public class MyHub : Hub
{
public void Send(string name, string message)
{
Clients.All.addMessage(name, message);
}
}
}
and I've entered the following commands:
Install-Package Microsoft.AspNet.SignalR.SelfHost
Install-Package Microsoft.Owin.Cors
Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Now the SignalR server works OK, but not the WebApi: it gives me "No HTTP resource was found that matches the request URI 'http://localhost:8080/api/values'". My controller class is the following:
namespace SignalRSelfHost
{
class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
}
Anyone can help me?
Set your ValuesController from private to public and it should work.

ImageResizer and ASP.NET Core Web Application (.NET Framework)

Where can I find a full sample of an ImageResizer (ImageResizing.net) standalone website based on ASP.NET Core Web Application (.NET Framework) ?
"frameworks": {
"net461": { }
},
Here's a working PoC that simulates what ImageResizer + AzureReader2 do.
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace ImageResizerSvc
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton(x =>
{
var config = new ImageResizer.Configuration.Config();
// install plugins, e.g.
// new PrettyGifs().Install(config);
return config;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute("imageresizer", "{*path}",
defaults: new { controller = "Images", action = "Resizer" });
});
}
}
}
ImagesController.cs
using ImageResizer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.WindowsAzure.Storage;
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace ImageResizerSvc.Controllers
{
public class ImagesController : Controller
{
private readonly ImageResizer.Configuration.Config _imageResizerConfig;
public ImagesController(ImageResizer.Configuration.Config imageResizerConfig)
{
_imageResizerConfig = imageResizerConfig ?? throw new ArgumentNullException(nameof(imageResizerConfig));
}
public async Task<IActionResult> Resizer()
{
// Init storage account
var connectionString = "UseDevelopmentStorage=true";
CloudStorageAccount.TryParse(connectionString, out CloudStorageAccount cloudStorageAccount);
var cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();
// Get blob ref
var storagePath = cloudBlobClient.BaseUri.ToString().TrimEnd('/', '\\');
var blobPath = Request.Path.Value.Trim('/', '\\');
var blobUri = new Uri($"{storagePath}/{blobPath}");
using (var sourceStream = new MemoryStream(4096))
{
try
{
var blob = await cloudBlobClient.GetBlobReferenceFromServerAsync(blobUri);
await blob.DownloadToStreamAsync(sourceStream);
sourceStream.Seek(0, SeekOrigin.Begin);
}
catch (StorageException e)
{
// Pass to client
if (Enum.IsDefined(typeof(HttpStatusCode), e.RequestInformation.HttpStatusCode))
{
return StatusCode(e.RequestInformation.HttpStatusCode, e.RequestInformation.HttpStatusMessage);
}
throw;
}
var destinationStream = new MemoryStream(4096);
var instructions = new Instructions(Request.QueryString.Value);
var imageJob = _imageResizerConfig.Build(new ImageJob(sourceStream, destinationStream, instructions));
destinationStream.Seek(0, SeekOrigin.Begin);
return File(destinationStream, imageJob.ResultMimeType);
}
}
}
}
Then you can use it by going to http://localhost/{container}/{blobPath.ext}?{imageResizer_queryString}
Imageflow.NET Server is the .NET Core equivalent to ImageResizer, but is much faster and produces much smaller image files. See https://github.com/imazen/imageflow-dotnet-server
If you want to do your own middleware, use Imageflow.NET directly. See https://github.com/imazen/imageflow-dotnet
[Disclaimer: I am the author of both ImageResizer and Imageflow]

Creating WCF Data Service operation with in-memory objects

I am trying to create a WCF DataService using in-memory object graph. This means that the backend is not an Entity Framework store, but a bunch of objects that reside in memory.
I am trying to create a service operation called GetUsersByName that has a single parameter for name and returns the matching users as an IQueryable collection.
I followed the documentation and added the access rules for this operation
config.SetServiceOperationAccessRule("GetUsersByName", ServiceOperationRights.All);
But when the SetServiceOperationAccessRule method is called I receive an exception on the client:
System.AggregateException was unhandled.
Here is the full code for my console application
using System;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Description;
using System.Data.Services;
using System.Data.Services.Common;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Collections.ObjectModel;
using System.Linq;
using System.Web;
using System.Net.Http;
using System.Net;
using System.IO;
namespace WCF_OData
{
class Program
{
static void Main(string[] args)
{
string serviceAddress = "http://localhost:8080";
Uri[] uriArray = { new Uri(serviceAddress) };
Type serviceType = typeof(UserDataService);
using (var host = new DataServiceHost(serviceType, uriArray)) {
host.Open();
var client = new HttpClient() { BaseAddress = new Uri(serviceAddress) };
Console.WriteLine("Client received: {0}", client.GetStringAsync("Users?$format=json").Result);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:8080");
request.Method = "GET";
request.Accept = #"application/json";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
Console.WriteLine(response.StatusCode);
Console.WriteLine(response.ContentType);
Console.WriteLine((new StreamReader(response.GetResponseStream())).ReadToEnd());
}
Console.WriteLine("Press any key to stop service");
Console.ReadKey();
}
}
}
[EnableJsonSupport]
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class UserDataService : DataService<UserService> {
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Users", EntitySetRights.All);
config.SetServiceOperationAccessRule("GetUsersByName", ServiceOperationRights.All);
config.UseVerboseErrors = true;
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}
public class UserService
{
private List<User> _List = new List<User>();
public UserService()
{
_List.Add(new User() { ID = 1, UserName = "John Doe" });
_List.Add(new User() { ID = 2, UserName = "Jane Doe" });
}
public IQueryable<User> Users
{
get
{
HttpContext x = HttpContext.Current;
return _List.AsQueryable<User>();
}
}
[OperationContract]
[WebGet(UriTemplate="GetUsersByName")]
public IQueryable<User> GetUsersByName(string name)
{
return new List<User>().AsQueryable();
}
}
[DataServiceKey("ID")]
public class User
{
public int ID { get; set; }
public string UserName { get; set; }
}
}
It looks like there are a few things going on here, so this may take a couple of iterations to work through. The first problem that should be fixed is the service operation. Service operations need to be declared on the class that inherits from DataService: "Service operations are methods added to the data service class that derives from DataService". Here's a sample:
using System.Data.Entity;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace Scratch.Web
{
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class ScratchService : DataService<ScratchEntityFrameworkContext>
{
static ScratchService()
{
Database.SetInitializer(new ScratchEntityFrameworkContextInitializer());
}
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
config.UseVerboseErrors = true;
}
[WebGet]
public IQueryable<Product> FuzzySearch(string idStartsWith)
{
var context = new ScratchEntityFrameworkContext();
return context.Products.ToList().Where(p => p.ID.ToString().StartsWith(idStartsWith)).AsQueryable();
}
}
}
You should then be able to call your service operation from a browser, with a URL format similar to the following: http://localhost:59803/ScratchService.svc/FuzzySearch()?idStartsWith='1'
Can we start by trying to get this functional in a browser and then see whether the AggregateException still happens?

ASP.NET Web API - Multiple POST methods on one controller?

I've been trying to add a second POST method to the default ValuesController class that will take an id parameter and act identical to the PUT method, like so:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
namespace WebCalendar.Controllers {
public class ValuesController : ApiController {
// GET /values
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
// GET /values/5
public string Get(int id) {
return "value";
}
// POST /values
public void Post(string value) {
}
// POST /values/5
public void Post(int id, string value) {
Put(id, value);
}
// PUT /values/5
public void Put(int id, string value){
}
// DELETE /values/5
public void Delete(int id) {
}
}
}
Problem is, when I add this second post method, any time I make a POST request, I get the error:
"No action was found on the controller 'values' that matches the request."
If I comment out one of the methods (doesn't matter which one), POST will work with the other method. I've tried renaming the methods, and even using [HttpPost] on both of them, but nothing has worked.
How can I have more than one POST method in a single ApiController?
EDIT
Here is the only route that I'm using:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { controller = "values", id = RouteParameter.Optional }
);
You have to include the action in your route:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);