I have a simple text binder to modify strings in my web api project. This binder should change all strings in all models.
public class TextoBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ValueProviderResult result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (result == null)
return false;
var value = result.AttemptedValue.Trim();
// value changes here
bindingContext.Model = value;
return true;
}
}
This TextoBinder class is registered as a Service:
config.Services.Insert(typeof(ModelBinderProvider), 0, new SimpleModelBinderProvider(typeof(string), new TextoBinder()));
The problem is that the Web Api doesn't call this binder when binding an input model as the MVC does in a similar application. I know the way the incomming data is treated is not the same in MVC and Web Api, but I could not make the text binder works for the inputs.
With a model-binding provider, you still need to add the [TextoBinder] attribute to the parameter, to tell Web API that it should use a model binder and not a media-type formatter.
You can take help of this.
Related
I like to create a custom Model Binder.
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(Wrapper<SomeType>))
{
return;
}
var model = // get the model as if I would be using the default ModelBindig i.e. Complex modelbinding
// manipulate 'model'
return ModelBindingResult.Success(model);
}
Any Suggestions on the get the model as If i would be using the dfault modelbinding ....
I think I might be misunderstanding how the TryUpdateModelAsync method works, I have a process where multipart form data is passed into mt controller in a PUT.
I have a requirement to trigger the model binding, use a custom model binder to pull the form data out and manipulate it as required and make it available for further processing.
I have tried to simplify the process for the purpose of this example and this is the simplest I can get :
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPut]
public async Task<IActionResult> Upload()
{
var mod = new TestModel();
await base.TryUpdateModelAsync(mod, "Model");
return new OkResult();
}
}
[ModelBinder(BinderType = typeof(TestModelBinder))]
public class TestModel
{
public string Name;
}
public class TestModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
bindingContext.Result = ModelBindingResult.Success(new TestModel()
{
Name = "Some Value"
});
return Task.CompletedTask;
}
}
So the process is
Client does a PUT to the Upload action.
upload action creates a new instance of a class (in this example the "TestModel")
we call TryUpdateModelAsync passing in the new (empty) model.
What I expected was for the name on the mod variable to be set to "Some Value" as that's what I have hard-coded the model binder to do, but it doesn't seem to work.
What am I doing wrong?
As far as I know, the TryUpdateModelAsync method is used for Manual model binding, it will not trigger custom model binder. Since the custom model binder has happened before you called TryUpdateModelAsync method. The TryUpdateModelAsync method will not recalled the custom model binder BindModelAsync method.
In my opinion, if you want to use Manual model binding, you could directly set the binder value to Some Value.
I am creating an asp.net core 2.2 api. I have several model classes, most of which contains a string property CreatorId:
public class ModelOne
{
public string CreatorId { get; set; }
//other boring properties
}
My controllers' actions accept these models with [FromBody] attribute
[HttpPost]
public IActionResult Create([FromBody] ModelOne model) { }
I don't want my web client to set the CreatorId property. This CreatorId field appears in tens of classes and I don't want to set it in the controlers actions manually like this:
model.CreatorId = User.Claims.First(claim => claim.Type == "id").Value;
I can't pollute the model classes with any custom model-binding related attributes. I don't want to pollute the controllers or actions with any model binding attributes.
Now, the question is: is there a way to add some custom logic after model is bound to check (maybe with the use of reflection) if model class contains CreatorId field and then to update this value. If it is not possible from ControllerBase User property, than maybe by looking at the jwt token. The process should be transparent for the developer, no attributes - maybe some custom model binder registered at the application level or some middleware working transparently.
Ok, I've invented my own - action filter based solution. It seems to work good. I've created a custom action filter:
public class CreatorIdActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ActionArguments.ContainsKey("request")) return;
var request = context.ActionArguments["request"];
var creatorIdProperty = request?.GetType().GetProperties().FirstOrDefault(x => x.Name == "CreatorId");
if (creatorIdProperty == null) return;
var user = ((ControllerBase)context.Controller).User;
creatorIdProperty.SetValue(request, user.Claims.First(claim => claim.Type == "id").Value);
}
public void OnActionExecuted(ActionExecutedContext context) { }
}
which was just registered in ConfigureServices
services.AddMvc(options => { options.Filters.Add(typeof(CreatorIdActionFilter)); })
no other boilerplate code is needed. Parameter name is always "request", that's why I take this from ActionArguments, but of course this can be implemented in more universal way if needed.
The solution works. I have only one question to some experts - can we call this solution elegant and efficient? Maybe I used some bad practices? I will be grateful for any comments on this.
I need to set user-specific culture for every web request sent to my web application written using ServiceStack 3 and MVC 4.
Each user's culture is stored in their profile in the database, which I retrieve into my own implementation of IAuthSession using a custom auth provider derived from CredentialsAuthProvider. So I don't care about the browser's AcceptLanguage header and instead want to set the current thread's culture to the Culture property of the auth session right after ServiceStack resolves it from the cache. This has to happen for both ServiceStack services and MVC controllers (derived from ServiceStackController).
What's the best way to accomplish the above?
UPDATE 1
I have found a way to do this, although I'm not convinced that this is the optimal solution.
In my base service class from which all services derive I overrode the SessionAs<> property as follows:
protected override TUserSession SessionAs<TUserSession>()
{
var genericUserSession = base.SessionAs<TUserSession>();
var userAuthSession = genericUserSession as UserAuthSession;
if (userAuthSession != null && !String.IsNullOrWhiteSpace(userAuthSession.LanguageCode))
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(userAuthSession.LanguageCode);
return genericUserSession;
}
where UserAuthSession is my custom implementation of ServiceStack's IAuthSession. Its LanguageCode property is set at login time to the user's chosen ISO culture code stored in the user's profile in the database.
Similarly, in my base controller class from which all my controllers derive I overrode the AuthSession property like so:
public override IAuthSession AuthSession
{
get
{
var userAuthSession = base.AuthSession as UserAuthSession;
if (userAuthSession != null && !String.IsNullOrWhiteSpace(userAuthSession.LanguageCode))
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(userAuthSession.LanguageCode);
return userAuthSession;
}
}
This seems to work fine because these two properties are used consistently whenever a service is invoked or a controller action is executed, so the current thread's culture gets set before any downstream logic is executed.
If anyone can think of a better approach please let me know.
UPDATE 2
Based on Scott's suggestion I created a custom AuthenticateAndSetCultureAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AuthenticateAndSetCultureAttribute : AuthenticateAttribute
{
public AuthenticateAndSetCultureAttribute() : base() { }
public AuthenticateAndSetCultureAttribute(ApplyTo applyTo) : base(applyTo) { }
public AuthenticateAndSetCultureAttribute(string provider) : base(provider) { }
public AuthenticateAndSetCultureAttribute(ApplyTo applyTo, string provider) : base(applyTo, provider) { }
public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto)
{
base.Execute(req, res, requestDto);
var session = req.GetSession() as UserAuthSession;
if (session != null && session.IsAuthenticated && !String.IsNullOrWhiteSpace(session.LanguageCode))
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(session.LanguageCode);
}
}
Because I only change the culture when the user is authenticated, it makes sense (in my mind anyways) to do it in the same place where we check for authentication.
I then decorated all my SS services and MVC controllers with this attribute instead of the original [Authenticate].
Now when a SS service is called the attribute's Execute method is executed, and the culture gets correctly set. However, Execute never gets executed when an MVC controller action is invoked, which is really puzzling because how then does MVC+SS know to redirect unauthenticated requests to the login page.
Any thoughts, anybody?
I would do this using a RequestFilter rather than overriding the SessionAs<T>. In your AppHost Configure method:
public override void Configure(Container container)
{
RequestFilters.Add((httpReq, httpResp, requestDto) => {
var session = httpReq.GetSession() as UserAuthSession;
if(session == null || !session.IsAuthenticated || String.IsNullOrWhiteSpace(session.LanguageCode))
return;
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(session.LanguageCode);
});
}
I ended up creating a custom MVC action filter that sets the request thread's culture based on the authenticated user's settings:
public class SetUserCultureAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var baseController = filterContext.Controller as BaseController;
if (baseController == null) return;
var userAuthSession = baseController.UserAuthSession;
if (userAuthSession != null && userAuthSession.IsAuthenticated && !String.IsNullOrWhiteSpace(userAuthSession.LanguageCode))
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(userAuthSession.LanguageCode);
}
}
Then I decorated my BaseController class with this attribute, and secured my controllers/actions with ServiceStack's regular Authorize attribute.
The previously created AuthenticateAndSetCultureAttribute that I originally intended to work for both controllers and services now is used for SS services only.
The culture is getting set correctly on both the MVC and the SS side, so I'm happy!
I have WebGet, and WebInvoke attributes describing my contract, but what is the best method of handling invalid URI's? Right now, if a user passes an URI that does not match my current operations, they get an "Endpoint not found." message. I want to pass back a more descriptive message.
For example, my URI template looks like:
/Stuff/{ID}/subStuff
but say they type
/Stuff/{ID}/OtherStuff
There is no such thing as OtherStuff, and I do not have a template for that.
Is there a way to cover all non mapped URI's with a single contract?
Thanks!
If you want to catch all the unhandled requests at a global level in WCF REST then you have to create a custom WebHttpBehavior and custom IOperationInvoker as described in this post.
If you want to return a custom error text with custom status code(404) you can also look into the WebOperationContext.OutgoingResponse property as described here.
While I did follow the links mark provided, and they did give a hint of what I needed. The answers that were linked did not actually answer my original question.
I was able to follow the steps, and I wanted to list my steps to solve this problem on this question as well.
To create my own response to any URI that was not mapped to a method in my contract I created the following:
A custom ServiceHostFactory
Behavior that I mapped to my end points within the custom ServiceHostFactory
a dispatcher that would handle all unmapped uri's that were provided to the service.
Below are the full definitions of the object's I created:
using System.ServiceModel;
using System.ServiceModel.Activation;
namespace your.namespace.here
{
public class CustomServiceHostFactory : WebServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses);
//note: these endpoints will not exist yet, if you are relying on the svc system to generate your endpoints for you
// calling host.AddDefaultEndpoints provides you the endpoints you need to add the behavior we need.
var endpoints = host.AddDefaultEndpoints();
foreach (var endpoint in endpoints)
{
endpoint.Behaviors.Add(new WcfUnkownUriBehavior());
}
return host;
}
}
}
As you can see above, we are adding a new behavior: WcfUnknownUriBehavior. This new custom behavior's soul duty is to replace the UnknownDispatcher. below is that implementation:
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
namespace your.namespace.here
{
public class UnknownUriDispatcher : IOperationInvoker
{
public object[] AllocateInputs()
{
//no inputs are really going to come in,
//but we want to provide an array anyways
return new object[1];
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
var responeObject = new YourResponseObject()
{
Message = "Invalid Uri",
Code = "Error",
};
Message result = Message.CreateMessage(MessageVersion.None, null, responeObject);
WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
outputs = new object[1]{responeObject};
return result;
}
public System.IAsyncResult InvokeBegin(object instance, object[] inputs, System.AsyncCallback callback, object state)
{
throw new System.NotImplementedException();
}
public object InvokeEnd(object instance, out object[] outputs, System.IAsyncResult result)
{
throw new System.NotImplementedException();
}
public bool IsSynchronous
{
get { return true; }
}
}
}
Once you have these objects specified, you can now use the new factory within your svc's "markup":
<%# ServiceHost Language="C#" Debug="true" Service="your.service.namespace.here" CodeBehind="myservice.svc.cs"
Factory="your.namespace.here.CustomServiceHostFactory" %>
And that should be it. as long as your object "YourResponseObject" can be serialized, it's serialized representation will be sent back to the client.