I've googled a whole day but still can't find the answer. I need to POST data via jQuery.post to Web API MVC-4 but unable to. This is my routing:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
and this is my Controller (the GET works!):
public string Get(int id)
{
return "value";
}
public void Post([FromBody]string data)
{
//body...
}
This is the jQuery.post:
$.post('api/mycontroller', { key1: 'val1' });
Any idea ?
Edit:
#Darin: I tried this:
public class UnitDetails{
public string id { get; set; }
}
and:
public void Post(UnitDetails id) {
//body...
}
and:
$.post('api/mycontroller', {id:'string1'});
But still I miss something.. it doesn't stop in Post(...){...}. Again - Get(...){...} does work.. ?
This is by design and the only way to make this work with a primitive type such as a string is the following:
$.post('/api/mycontroller', '=' + encodeURIComponent('val1'));
So the body of the POST request must contain the following:
=val1
instead of:
data=val1
This has been discussed in this thread.
As an alternative you could define a view model:
public class MyViewModel
{
public string Data { get; set; }
}
and then have your controller action take this view model as parameter:
public void Post(MyViewModel model)
{
//body...
}
Contrary to primitive types, complex types use formatters instead of model binding. Here's an article which covers how does the Web API does parameter binding.
You're posting to api/mycontroller. ASP.NET MVC automatically appends the name supplied with 'Controller', so it's looking for a controller named mycontrollerController. The name of your API controller is not mentioned in your post, but I suspect it's not that.
Assuming that your controller is named 'myController', try posting to api/my.
$.post('api/my', { id: 'string1' });
Related
With ServiceStack's Razor Story we have a variety of ways of selecting which Razor View we want to use to render a page. Even better, and critical in my case, is we can pass in a Content-Type header (or query string parameter, or even page "suffix") as well to return the raw model in a variety of formats.
Is there any way to use ServiceStack Templates (now known as SharpScript) to do the same thing? I follow the example here but I just get back the standard HTML format response. It doesn't use my template, no matter how named.
Following the example in the v5.5 Release Notes:
[Route("/hello/{Name}")]
public class Hello : IReturn<HelloResponse>
{
public string Name { get; set; }
}
public class HelloResponse
{
public string Result { get; set; }
}
public class HelloService : Service
{
public object Any(Hello request) => new HelloResponse { Result = $"Hello, {request.Name}!" };
}
Going to /hello/World?format=html provides me the standard HTML report, not my template. I followed another example to force it to use the template ....
public object Any(Hello request) =>
new PageResult(Request.GetPage("examples/hello")) {
Model = request.Name
};
... and it ALWAYS returns my template, even if I specify /hello/World?format=json.
Is there any way to have Razor-like view selection for ServiceStack + ScriptSharp pages, but also support different response formats?
It's hard to answer a vague question like this without details of a specific scenario you want to achieve that's not working.
You can return Sharp Pages in a number of ways:
When it's requested directly as a content page, e.g /dir/page -> /dir/page.html
Using Page Based Routing, e.g /dir/1 -> /dir/_id.html
As a View Page in response to a Service when it's named after the Request DTO or Response DTO, e.g /contacts/1 -> /Views/GetContact.html or /Views/GetContactResponse.html
Select which view to render inside your Service by returning your Response DTO inside a custom HttpResult:
public object Any(MyRequest request)
{
...
return new HttpResult(response)
{
View = "CustomPage", // -> /Views/CustomPage.html
//Template = "_custom-layout",
};
}
Add the [ClientCanSwapTemplates] Request Filter attribute to let the View and Template by modified on the QueryString, e.g: ?View=CustomPage&Template=_custom-layout
[ClientCanSwapTemplates]
public object Any(MyRequest request) => ...
Choosing which page you want to render inside your Model View Controller Service by returning a custom PageResult:
public class CustomerServices : Service
{
public object Any(ViewCustomer request) =>
new PageResult(Request.GetPage("examples/customer")) {
Model = TemplateQueryData.GetCustomer(request.Id)
};
}
Note: That the SharpPagesFeature resolves pages using your cascading AppHost.VirtualFileSources. In .NET Core it's configured to use its WebRoot, e.g /wwwroot.
For Sharp Pages to return its Response in Multiple Content Types:
as well to return the raw model in a variety of formats.
You need to use a Sharp APIs which return a value, e.g. /hello/_name/index.html:
{{ { result: `Hello, ${name}!` } | return }}
To succinctly answer my own question, the first option from #mythz is what I needed. After calling Plugins.Add(new SharpPagesFeature()) in my AppHost, I needed to return HttpResult from my service method:
public object Any(MyRequest request)
{
...
return new HttpResult(response)
{
View = "CustomPage", // -> /Views/CustomPage.html
//Template = "_custom-layout",
};
}
I am trying to call a method in a webapiController(.net core) from my test
If my request object has an Id as string it does not work ,as an int it does
what Am I doing wrong in my noddy sample?
[Fact]
public async Task WhyDoesNotWorkWithIdAsString()
{
string thisQueryDoesNotWork = "http://localhost:1111/api/v1/shop/customers?id=1";
string thisQueryWorksProvidedTheIdIsAnInt = "http://localhost:1111/api/v1/shop/customers/1";
var response = await client.GetAsync(thisQueryDoesNotWork);
var response2 = await client.GetAsync(thisQueryWorksProvidedTheIdIsAnInt);
//omitted asserts
}
[Route("api/[controller]")]
public class ShopController: Controller
{
[HttpGet]
[Route("{id}",Name ="GetCustomerAsync")]
[ProducesResponseType(typeof(GetCustomerResponse), (int)HttpStatusCode.OK)]
//more ProducesResponseType omitted
public async Task<IActionResult> GetCustomerAsync([FromQuery]GetCustomerRequest request)
{
//code omitted
}
}
public class GetCustomerRequest
{
Required]
public string Id { get; set; }
// public int Id { get; set; } //works with int but not with a string
}
}
Also is below correct
[FromQuery]=use Get only
[FromBody]=use Put-Post
is there a link with explanation when to use this parameter binding?
many thanks
In
[Route("{id}",Name ="GetCustomerAsync")]
{id} template parameter is part of the route but in the action parameter is it being requested via [FromQuery], which is why it is not matching.
It is expecting
http://localhost:1111/api/v1/shop/customers/1
But your are sending
http://localhost:1111/api/v1/shop/customers?id=1
which is why the second link works and the first does not.
Reference Routing to Controller Actions
As for the concern about [From*] attributes
[FromHeader], [FromQuery], [FromRoute], [FromForm]: Use these to specify the exact binding source you want to apply.
...
[FromBody]: Use the configured formatters to bind data from the request body. The formatter is selected based on content type of the request.
Reference Model Binding in ASP.NET Core
I have found out what the problem is, Name of the httpget or route must match the one you setup in the link
Example: I have a countries catalog stored in another DB and I need to use it as a property in some ContentParts. I'm trying to make the connection without interfering much with Orchard wiring.
public class MoviePart : ContentPart<MoviePartRecord>
{
public IEnumerable<CountryRecord> Countries
{
get
{
return Record.Countries.Select(r => r.CountryRecord);
}
}
}
The relation between CountryRecords and MovieParts will be on the Orchard DB, but the CountryRecord data is in another DB. I only need Read access, but I don't get which and how to override the Handler to use the other source.
Do I need to create a ContentHandler and override all methods, and create another StorageFilter that uses the new repository with the external source? And how would I inject the new repo into the handler?
public class CountryPartHandler : ContentHandler
{
public CountryPartHandler(IRepository<CountryPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
protected override void Loading(LoadContentContext context)
{
base.Loading(context);
}
}
Update:
In this Using External Data with Orchard (around 25th min) video, he seems to be doing what I need with this code:
public ProductPartHandler(IRepository<ProductPartRecord> repository, Work<IProductService> productServiceWork)
{
Filters.Add(StorageFilter.For(repository));
OnActivated<ProductPart>((context, part) => {
part.ProductField.Loader(() => productServiceWork.Value.GetProduct(part.Id));
});
}
But in my code it can't find the "Loader" function, even though I have all the references from the video too, so maybe ProductField is a custom type?
So that is a lazy field on the part, something like this:
public class MyPart : ContentPart {
internal readonly LazyField<CustomData> CustomDataField = new LazyField<CustomData>();
public CustomData CustomData {
get { return CustomDataField.Value; }
}
}
public class CustomData {
...
}
public class MyPartHandler : ContentPartHandler {
private ICustomService _customService;
public MyPartHandler(ICustomService customService){
_customService = customService;
OnActivated<MyPart>(Initialize);
}
private void Initialize(ActivatedContentContext context, MyPart part){
part.CustomDataField.Loader(() => {
return _customService.Get(part.ContentItem.Id);
});
}
}
I don't know how you are loading your external data, whether via rest, wcf etc., but the logic can just be thrown into the custom service
What is the best practice for placing business logic in message based design?
Im using servicestack for building my api.
The wiki shows the example of placing the RequiredRole Attribute on the message instead of the service handling it.
In a sense this [RequiredRole]/[Authenticate] is business logic/security attached to the message.
Concrete example
Say for example i would add DeleteAddress message:
public class DeleteAddress : IReturn<bool>
{
public int AddressId { get; set; }
}
But for this to be properly secure i want to check either Admin Role, permission to ManageAllAddresses or that the AddressId is linked to this user (maybe in session, maybe through a db call).
How would i best go about this?
Proposition
Is the following code the good practice and if so how would i implement it?
[RequiredRole("Admin")]
[RequiredPermission("ManageAllAddresses ")]
[RequiredAddressLinkedToAccount]
public class DeleteAddress : IReturn<bool>
{
public int AddressId { get; set; }
}
ServiceStack's recommendation is to keep your ServiceModel free of dependencies so we'd recommend to annotate your Service implementation classes instead which you can annotate either on the Service class to apply to all Operations or on the individual methods to apply just to that operation, e.g:
[RequiredRole("Admin")]
public class AddressServices : Service
{
[RequiredPermission("ManageAllAddresses ")]
[RequiredAddressLinkedToAccount]
public object Any(DeleteAddress request)
{
}
}
Please note ServiceStack requires your Services to return reference types, which is typically a Response DTO but can also be a string, e.g:
public class DeleteAddress : IReturn<string>
{
public int AddressId { get; set; }
}
To finish of this question. I could make a request filter and add it on the service.
Either inherit from AuthenticateAttribute or Directly from RequestFilterAttribute.
public class RequiredAddressLinkedToAccount : AuthenticateAttribute
{
public RequiredRoleAttribute(ApplyTo applyTo)
{
this.ApplyTo = applyTo;
this.Priority = (int)RequestFilterPriority.RequiredRole;
}
public override void Execute(IRequest req, IResponse res, object requestDto)
{
var dto = requestDto as ILinkedToAccount;
var session = req.GetSession();
if(dto.AccountId == session.Id)
return; //we dont want anything to be blocked if the account Id is there.
//Implement like RequireRoleAttribute
if (DoHtmlRedirectIfConfigured(req, res))
return;
res.StatusCode = (int)HttpStatusCode.Forbidden;
res.StatusDescription = "Address does not belong to you";
res.EndRequest();
}
}
I would like to post a JSON object to my service stack service and use a dynamic property in the request DTO. All approaches I have tried so far leave the object being a NULL value.
The javascript code I use:
$.getJSON(
"/api/json/reply/Hello",
{
Name: "Murphy",
Laws: {
SomeProp: "A list of my laws",
SomeArr: [
{ Title: "First law" },
{ Title: "Second law" },
{ Title: "Third law" }
]
}
},
function(data) {
alert(data.result);
}
);
The DTO to receive the request:
public class Hello
{
public string Name { get; set; }
public dynamic Laws { get; set; }
}
I also tried to use an object and JsonObject instead of dynamic in the DTO.
To be complete, here's the service too:
public class HelloService : Service
{
public object Any(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
Murphy comes through in the Name property without any problems, but the Laws property remains NULL.
In the end, I want to somehow iterate (using reflection?) over the Laws property and get all the contained properties and values.
I cannot use a typed DTO here, because I don't know the JSON of the Laws property at development time (and it can change quite frequently).
Thanks for any help!
The .NET 3.5 library builds of ServiceStack on NuGet doesn't have native support for the .NET 4.0+ dynamic type. You can pass JSON into a string property and dynamically parse it on the server:
public object Any(Hello request)
{
var laws = JsonObject.Parse(request.Laws);
laws["SomeProp"] //
laws.ArrayObjects("SomeArr") //
}
Otherwise You can use Dictionary<string,string> or if you specify in your AppHost:
JsConfig.ConvertObjectTypesIntoStringDictionary = true;
You can use object which will treat objects like a string dictionary.
Otherwise dynamic shouldn't be on the DTO as it's meaningless as to what the service expects. You could just add it to the QueryString. You can use the JSV Format to specify complex object graphs in the QueryString, e.g:
/hello?laws={SomeProp:A list of my laws,SomeArr:[{Title:First Law}]}
Note: the spaces above gets encoded with %20 on the wire.
Which you can access in your services with:
public object Any(Hello request)
{
var laws = base.QueryString["laws"].FromJsv<SomeTypeMatchingJsvSent>();
}