#c# #asp.net #json #post #asp.net-web-api
#c# #asp.net #json #Публикация #asp.net-web-api
Вопрос:
У меня есть веб-сайт на C # и веб-API. В части веб-API у меня есть четыре контроллера (для продуктов, категорий, пользователей и заказов). Обычные GetAllProducts, getProduct и т. Д., Которые используются контроллером API MVC по умолчанию, Работают нормально, но теперь мне нужен метод действия для HttpPost (для сохранения заказов).
Вот ниже то, что у меня было до того, как я захотел добавить это действие сохранения (PS: внизу вы можете увидеть, что я пытался заставить действие сохранения работать):
WebApiConfig.cs:
using WebMatrix.WebData;
using System.Web.Security;
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Only allow JSON response format
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
// Default API HttpRoute
// Call examples: "/api/products" or "/api/products/2"
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Configure the DependencyResolver and register all API-Controllers
var container = new UnityContainer();
container.RegisterType<IProductsRepository, ProductsRepository>(new HierarchicalLifetimeManager());
container.RegisterType<ICategoriesRepository, CategoriesRepository>(new HierarchicalLifetimeManager());
container.RegisterType<IOrdersRepository, OrdersRepository>(new HierarchicalLifetimeManager());
container.RegisterType<IUsersRepository, UsersRepository>(new HierarchicalLifetimeManager());
config.DependencyResolver = new ControllerDependencyFactory(container);
// Some other stuff
...
}
}
ControllerDependencyFactory.cs:
// This class uses the Inversion of Control (IoC) pattern, which creates
// instances of objects for you. All Web API Objects (API Controllers) are
// registered with this IDependencyResolver, so with a GET-request to the API
// this class will automatically create the correct instance of that API Controller.
public class ControllerDependencyFactory : IDependencyResolver
{
protected IUnityContainer container;
public ControllerDependencyFactory(IUnityContainer container)
{
if (container == null)
throw new ArgumentNullException("container");
this.container = container;
}
public object GetService(Type serviceType)
{
try
{
return container.Resolve(serviceType);
}
catch (ResolutionFailedException)
{
// WARNING: Do not throw any errors, we need to return null here
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return container.ResolveAll(serviceType);
}
catch (ResolutionFailedException)
{
// WARNING: Do not throw any errors, we need to return an empty List here
return new List<object>();
}
}
public IDependencyScope BeginScope()
{
var child = container.CreateChildContainer();
return new ControllerDependencyFactory(child);
}
public void Dispose()
{
container.Dispose();
}
}
IProductsRepository.cs:
public interface IProductsRepository
{
IEnumerable<ProductsAPI> GetAll();
ProductsAPI Get(int id);
// No need for an Add, Update or Remove
}
ProductsRepository.cs:
public class ProductsRepository : IProductsRepository
{
private List<ProductsAPI> products;
private ProductController controller;
public ProductsRepository()
{
// Get the Controller-instance from the RouteMaps instead of creating a new instance here
getControllerInstance();
products = controller.ListAll();
}
public IEnumerable<ProductsAPI> GetAll()
{
return products;
}
public ProductsAPI Get(int id)
{
return products.Find(p => p.ProductId == id);
}
// No need for an Add, Update or Remove
private void getControllerInstance()
{
var url = "~/product";
// Original path is stored and will be rewritten in the end
var httpContext = new HttpContextWrapper(HttpContext.Current);
string originalPath = httpContext.Request.Path;
try
{
// Fake a request to the supplied URL into the routing system
httpContext.RewritePath(url);
RouteData urlRouteData = RouteTable.Routes.GetRouteData(httpContext);
// If the route data was not found (e.g url leads to another site) then authorization is denied.
// If you want to have a navigation to a different site, don't use AuthorizationMenu
if (urlRouteData != null)
{
string controllerName = urlRouteData.Values["controller"].ToString();
// Get an instance of the controller that would handle this route
var requestContext = new RequestContext(httpContext, urlRouteData);
var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
var controllerbase = (ControllerBase)controllerFactory.CreateController(requestContext, controllerName);
controller = (ProductController)controllerbase;
}
}
finally
{
// Reset our request path.
httpContext.RewritePath(originalPath);
}
}
ProductsController.cs (only used by the Web API):
public class ProductsController : ApiController
{
private IProductsRepository repository;
public ProductsController()
{
repository = new ProductsRepository();
}
// GET /api/products
public IEnumerable<ProductsAPI> GetAllProducts()
{
if(LockAPI.loggedIn)
return repository.GetAll();
return null;
}
// GET /api/products/5
public ProductsAPI GetProduct(int id)
{
if (LockAPI.loggedIn)
{
ProductsAPI item = repository.Get(id);
if (item == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
return item;
}
return null;
}
}
ProductController.cs (also used by the Website):
public class ProductController : Controller
{
private IMyDatabase db;
public ProductController()
{
this.db = new MyDatabase();
}
// Some methods used by the Website part
...
// List all products
// This method is only used for the Web API part of this project, that will be used in the Mobile App
// Return: actual List of Products, instead of JSON format
public List<ProductsAPI> ListAll()
{
List<ProductsAPI> products = db.Products
.Where(p => p.Visible == true)
.OrderBy(p => p.Name)
.Select(p => new ProductsAPI()
{
ProductId = p.ProductId,
Name = p.Name,
Price = p.Price,
CategoryId = p.CategoryId,
})
.ToList();
return products;
}
}
ProductsAPI.cs:
public class ProductsAPI
{
// The Products Fields we use in the Mobile App
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
}
So that’s what I had, and I’ve added/changed the following:
In WebApiConfig.cs changed:
// Default API HttpRoute
// Call examples: "/api/products" or "/api/products/2"
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = @"^d $" } // Only integers
);
to:
// Default API HttpRoute
// Call example: "/api/orders"
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}"
);
// API HttpRoute with ID
// Call example: "api/orders/2"
config.Routes.MapHttpRoute(
name: "ApiWithID",
routeTemplate: "api/{controller}/{id}",
defaults: null,
constraints: new { id = @"^d $" } // Only integers
);
// Api HttpRoute with Action
// Call example: "api/orders/save"
config.Routes.MapHttpRoute(
name: "ApiWithAction",
routeTemplate: "api/{controller}/{action}"
);
В OrdersController.cs (используется только веб-API) добавлен следующий метод:
// POST /api/orders/save
[HttpPost]
[ActionName("save")]
public Boolean SaveOrder([FromUri] string UniqueID, [FromBody] dynamic Data)
{
if(LockAPI.loggedIn)
{
// Convert JSON body to a Key-Value Dictionary
Dictionary<string, object> data = JsonConvert.DeserializeObject<Dictionary<string, object>>(Convert.ToString(Data));
// Convert data-objects
...
// Use this converted data to save the Orders
// and return true or false based on the result
...
}
return false;
}
Теперь я использую FireFox RestClient:
Метод: POST - http://localhost/api/orders/save
Тело: "productIds": "[20, 25]", "prices": "[0.40, 7.40]", dateInTicks: "1402444800"
И получите следующий ответ:
Status Code: 404 Not Found
Cache-Control: no-cache
Connection: Close
Content-Length: 212
Content-Type: application/json; charset=utf-8
Date: Tue, 24 Jun 2014 08:45:10 GMT
Expires: -1
Pragma: no-cache
Server: ASP.NET Development Server/11.0.0.0
X-AspNet-Version: 4.0.30319
{
"$id": "1",
"Message": "No HTTP resource was found that matches the request URI 'http://localhost/api/orders/save'.",
"MessageDetail": "No action was found on the controller 'Orders' that matches the request."
}
Как получить HttpPost для веб-API C # mvc для работы с действиями?
Комментарии:
1. сигнатура метода сохранения не соответствует параметрам, которые вы предоставляете. Попробуйте создать фиктивную функцию сохранения с параметрами 0 и 1 и посмотреть, какой из них попадает по этому URL
Ответ №1:
Сначала я снова попробовал другой MapHttpRoute:
// Default API HttpRoute
// Call examples: "/api/products/all" or "/api/products/one/2" or "/api/orders/save" or "/api/test/enable"
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
и небольшое изменение в контроллерах веб-API:
- Добавлено
[ActionName("all")]
в GetAll-методы (это изменяется/api/products
на/api/products/all
) - Добавлено
[ActionName("one")]
в Get-методы (это изменяется/api/products/2
на/api/products/one/2
)
и изменил метод SaveOrder в OrdersController на:
public Boolean SaveOrder([FromBody] string productIds, [FromBody] string newPrices, [FromBody] string dateInTicks)
{
...
}
Когда я снова сделал тот же пост с помощью FireFox RestClient, у меня была ошибка 500:
Status Code: 500 Internal Server Error
Cache-Control: no-cache
Connection: Close
Content-Length: 226
Content-Type: application/json; charset=utf-8
Date: Tue, 24 Jun 2014 14:33:51 GMT
Expires: -1
Pragma: no-cache
Server: ASP.NET Development Server/11.0.0.0
X-AspNet-Version: 4.0.30319
{
"$id": "1",
"Message": "An error has occurred.",
"ExceptionMessage": "Can't bind multiple parameters ('productIds' and 'dateInTicks') to the request's content.",
"ExceptionType": "System.InvalidOperationException",
"StackTrace": null
}
Это имело гораздо больше смысла.
Затем я изменил метод сохранения на:
public Boolean SaveOrder([FromBody] JObject jsonData)
{
// TODO: Seperate the JSON Data
// Rest the same as before
...
}
И теперь он переходит к методу, и я могу отлаживать его.
Прямо сейчас у меня все еще есть проблемы с преобразованием JSON в теле в фактические разделенные данные, но это что-то для другого вопроса.