WCF rest service to accept dynamic as parameter - asp.net

In my application, I am sending a json object to a service and at the service end, I expect an object of type dynamic
public bool TestService(dynamic entity)
{
// process
}
When i debug and see the entity filled, I am not able to type cast it. Any idea how i can retrieve fields from the entity sent

I'm curious - if you're sending up a JSON formatted object, why not have your service method accept a string and then use something like JSON.net to cast it to the appropriate type?
public bool TestService(string entity)
{
var myObject = JsonConvert.DeserializeObject<MyObjectType>(entity);
//do stuff with myObject...
}
Or you could deserialize it into an anonymous object:
public bool TestService(string entity)
{
var myAnonymousObject = new { Name = String.Empty, Address = String.Empty };
var myObject = JsonConvert.DeserializeAnonymousType(entity, myAnonymousObject);
//do stuff with myObject
}
I guess I'm not sure why your JSON formatted object needs to be dynamic.

Related

How can I use a default value/model on WebAPI EmptyBody?

I have dotnet WebAPI and I'm trying to get a specific behaviour but am constantly getting 415 responses.
I have reproduced this by starting a new webapi project using dotnet new webapi on the command line. From there, I added two things: a new controller, and a model class. In my real project the model class is obviously a bit more complex, with inheritance and methods etc...
Here they are:
[HttpGet("/data")]
public async Task<IActionResult> GetModel(BodyParams input)
{
var response = new { Message = "Hello", value = input.valueOne };
return Ok(response);
}
public class BodyParams {
public bool valueOne { get; set; } = true;
}
My goal is that the user can call https://localhost:7222/data with no headers or body needed at all, and will get the response - BodyParams will be used with the default value of true. Currently, from postman, or from the browser, I get a 415 response.
I've worked through several suggestions on stack and git but nothing seems to be working for me. Specifically, I have tried:
Adding [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] into the controller, but this makes no difference unless I provide an empty {} json object in the body. This is not what I want.
Making BodyParams nullable - again, no change.
Adding .AddControllers(opt => opt.AllowEmptyInputInBodyModelBinding = true)... again, no change.
I Implemented the solution suggested here using the attribute modification in the comment by #HappyGoLucky. Again, this did not give the desired outcome, but it did change the response to : 400 - "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true."
I tried modifying the solution in (4) to manually set context.HttpContext.Request.Body to an empty json object... but I can't figure out the syntax for this because it need to be a byte array and at that point I feel like I am way over complicating this.
How can I get the controller to use BodyParams with default values in the case that the user provides no body and no headers at all?
You can achieve that using a Minimal API.
app.MapGet("/data",
async (HttpRequest httpRequest) =>
{
var value = true;
if (Equals(httpRequest.GetTypedHeaders().ContentType, MediaTypeHeaderValue.Parse("application/json")))
{
var bodyParams = await httpRequest.ReadFromJsonAsync<BodyParams>();
if (bodyParams is not null) value = bodyParams.ValueOne;
}
var response = new {Message = "Hello", value};
return Results.Ok(response);
});
So, as there doesn't seem to be a more straightforward answer, I have currently gone with the approach number 5) from the OP, and just tweaking the code from there very slightly.
All this does is act as an action which checks the if the user has passed in any body json. If not, then it adds in an empty anonymous type. The behaviour then is to use the default True value from the BodyParams class.
The full code for the action class is:
internal class AllowMissingContentTypeForEmptyBodyConvention : Attribute, IActionModelConvention
{
public void Apply(ActionModel action)
{
action.Filters.Add(new AllowMissingContentTypeForEmptyBodyFilter());
}
private class AllowMissingContentTypeForEmptyBodyFilter : IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (!context.HttpContext.Request.HasJsonContentType()
&& (context.HttpContext.Request.ContentLength == default
|| context.HttpContext.Request.ContentLength == 0))
{
context.HttpContext.Request.ContentType = "application/json";
var str = new { };
//convert string to jsontype
var json = JsonConvert.SerializeObject(str);
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
context.HttpContext.Request.Body = new MemoryStream(requestData);
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
// Do nothing
}
}
}
Then you can add this to any of your controllers using [AllowMissingContentTypeForEmptyBodyConvention]

Can I create a custom Authorization policy that I can pass in fields received from my controller method?

I want to have the parameter I receive in the controller from a client be used to authorize my "POST" methods used in my projects(Users can only edit projects they are assigned to). UserIds will live on the project so I need to fetch the project and verify the current user id is in the project.
// I want to modify my policy below where I can use "projectId". ProjectId is dynamic and passed in from a ajax call
[Authorize(Policy = "CanModifyProject")]
[HttpPost]
public async Task<IActionResult> SaveWorker(var projectId, workerModel worker)
{
// Code here....
}
My policy
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanModifyProject requirement)
{
bool isSuperAdmin = context.User.IsAdmin();
bool isAdmin = context.User.IsAdmin();
var currentUserId = context.User.GetUserId();
// I NEED TO HAVE THE DYNAMIC PROJECT ID here recieved by "POST" methods
int projectId = 0;
// Check to see if the project has the assigned User
var projectUserIsAssigned = this.projectRepository.ProjectIsAssignedToUser(projectId currentUserId);
if (isSuperAdmin || isAdmin && projectUserIsAssigned)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
else
{
context.Fail();
return Task.CompletedTask;
}
}
How can I access my ProjectId in my post method ???
The authorization phase is fairly early in the request processing pipeline, before executing the action. So no view model is bound yet (by model binding). I would use an IActionFilter or IAsyncActionFilter for this purpose. There you can access the ActionExecutingContext.ActionArguments to get the bound parameter you want for your custom authorization.
Here I would like to try solving it the way you want with the code usually executed in the scope of IAuthorizationFilter or IAsyncAuthorizationFilter (there is another place in which it's executed in the AuthorizationMiddleware). With this approach, it's limited in how you get the action argument value. It should be plainly accessible via some key and the raw value we get is of course a string. As in your specific requirement, it's just a parameter of int which can be sent via form-data or query string from the client.
The point here is we can get such a raw value using a CompositeValueProvider.
The code should say it all:
//inject IOptions<MvcOptions> into your CanModifyProject requirement class
//name it as _mvcOptions
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanModifyProject requirement) {
bool isSuperAdmin = context.User.IsAdmin();
bool isAdmin = context.User.IsAdmin();
var currentUserId = context.User.GetUserId();
var projectId = 0;
//get projectId
if(context.Resource is ActionContext ac &&
ac.ActionDescriptor is ControllerActionDescriptor ca)
{
//match only the related controller method
if(ca.ControllerTypeInfo == typeof(YourController) &&
ca.MethodInfo.Name == nameof(YourController.SaveWorker)) {
//create the composite value provider
var valueProvider = await CompositeValueProvider.CreateAsync(ac, _mvcOptions.Value.ValueProviderFactories);
//get the raw id (as string)
var id = valueProvider.GetValue("projectId").FirstValue ?? "";
//parse your projectId here
int.TryParse(id, out projectId);
}
}
//your remaining code (unchanged)
//...
}

How to return IEnumerable list of objects with WebApi?

I have an object called PostMapDB and a method which retrieves a list of those objects. I would like to be able to pass this list and retrieve the data using webapi.
The code bellow gets me an error:{"":["The input was not valid."]}
[HttpGet]
public string Get(PostMapDB list)
{
IEnumerable<PostMapDB> dataOfPosts = list.getAllPosts().OrderBy(x => x.Date);
var data = JsonConvert.SerializeObject(dataOfPosts, new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver()
{
IgnoreSerializableAttribute = false
}
});
return data;
}
How does your request to server looks like?
What's the definition on PostMapDB?
Make sure you're passing data in a right way.
Probably the attribute FromBody would help:
public string Get([FromBody] PostMapDB list)
Reference: https://learn.microsoft.com/en-gb/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api

WEB.API returning string property as json

We have a fun situation where we are storing json as a string in SQL Server. We don't not care what is in this object its pretty much a passthrough property. Passthrough meaning we just save it for clients and return it as is. We never read it in C#. I'm storing it as a nvarchar in the database but I'm trying to figure out how i can automagically serialize that string into a json object to return to the client. I dont want to have to in javascript call fromJson.
We are using Newtonsoft as our Json Serializer. Here is the highlevel setup:
DTO:
public class MyDto{
public dynamic SessionBag { get;set;}
}
Entity Framework Entity:
public class My{
public string SessionBag { get;set;}
}
A client would post/put us:
{"SessionBag":{"Name":"test"}}
We would then save it in the db as a string:
"{"Name":"test"}"
How can I serialize this so when it returns from Web.API it looks like:
{
SessionBag:{
Name: "test"
}
}
I'm currently messing around trying to get it to save using dynamic / object. But i can't figure how how to return it as a json object. I would love to figure out how to do this with just annotations.
Here is how I convert it to a string to save:
if (dto.SessionBag != null){
var serializer = JsonSerializer.Create(new JsonSerializerSettings(){
NullValueHandling = NullValueHandling.Ignore
});
using (var writer = new StringWriter()){
serializer.Serialize(writer, dto.SessionBag);
entity.SessionData = writer.ToString();
}
}
In case its helpful our WebApiControllers are pretty simple and just return an IHttpActionResult with the dto. All feedback is welcome.
So I think i figured it out. In my dto:
[JsonIgnore]
public string SessionBagString { get; set; }
public JObject SessionBag
{
get
{
if (!string.IsNullOrEmpty(SessionBagString))
{
return JObject.Parse(SessionBagString);
}
return null;
}
set
{
if(value != null)
{
SessionBagString = value.ToString();
}
}
}
In my repo code I now have:
if (dto.SessionBag != null)
{
entity.SessionBagString = dto.SessionBagString;
}
That pretty much worked for me. Let me know if there is a better way to do it.

AuthorizationManager based on service invocation parameters

I'm currently developing my own AuthorizationManager, it looks something like that:
public class MyAuthorizationManager : ServiceAuthorizationManager
{
static bool initialize = false;
public override bool CheckAccess(OperationContext operationContext)
{
ServiceSecurityContext context = ServiceSecurityContext.Current;
string[] roles = Roles.GetRolesForUser(operationContext.ServiceSecurityContext.PrimaryIdentity.Name);
return roles.Count() > 0;
}
public override bool CheckAccess(OperationContext operationContext, ref System.ServiceModel.Channels.Message message)
{
MessageBuffer buffer = operationContext.RequestContext.RequestMessage.CreateBufferedCopy(int.MaxValue);
message = buffer.CreateMessage();
Console.WriteLine(message);
return base.CheckAccess(operationContext, ref message);
}
}
I would like to perform authorization check based on a service contract parameter, in example, if contract looks like:
[ServiceContract]
public interface IServerContract
{
[OperationContract]
[ServiceKnownType(typeof(ChildTypeOne))]
[ServiceKnownType(typeof(ChildTypeTwo))]
string SecuredMessage(ParentType incoming);
}
My goal is authorizing depending on type, in example, authorizing if incoming date is ChildTypeOne and deniying in case it was ChildTypeTwo.
I've checked "Message" and it looks like:
It must be decrypted
Seems to be highly dependent on binding
Is there any easy way to simply get parameter type?
Ok, i've figured out how to perform that. Anyway, if you know any better way to do so, let me know:
Here is the AuthorizationManager i'm using:
public class MyAuthorizationManager : ServiceAuthorizationManager
{
static bool initialize = false;
public override bool CheckAccess(OperationContext operationContext, ref System.ServiceModel.Channels.Message message)
{
bool returnedValue = base.CheckAccess(operationContext, ref message);
// messags in WCF are always read-once
// we create one copy to work with, and one copy to return back to the plumbing
MessageBuffer buffer = operationContext.RequestContext.RequestMessage.CreateBufferedCopy(int.MaxValue);
message = buffer.CreateMessage();
// get the username vale using XPath
XPathNavigator nav = buffer.CreateNavigator();
StandardNamespaceManager nsm = new StandardNamespaceManager(nav.NameTable);
nav = nav.SelectSingleNode("//#i:type",nsm);
returnedValue &= (nav.ToString() == "a:"+typeof(ChildTypeOne).Name);
return returnedValue;
}
public class StandardNamespaceManager : XmlNamespaceManager
{
public StandardNamespaceManager(XmlNameTable nameTable)
: base(nameTable)
{
this.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/");
this.AddNamespace("s11", "http://schemas.xmlsoap.org/soap/envelope/");
this.AddNamespace("s12", "http://www.w3.org/2003/05/soap-envelope");
this.AddNamespace("wsaAugust2004", "http://schemas.xmlsoap.org/ws/2004/08/addressing");
this.AddNamespace("wsa10", "http://www.w3.org/2005/08/addressing");
this.AddNamespace("i", "http://www.w3.org/2001/XMLSchema-instance");
}
}
}
Previous AuthorizationManager will work rejecting "ChildTypeTwo". You can use a RoleProvider in order to get role based on type.

Resources