I can t use SessionService class in asp.net web api - c#

I have a class derived from webapi controller.In that controller i have a method
[HttpGet]
[Route("MessageCount")]
public string GetMessageCountCount()
{
OrganisationMembership organisationMembership = organisationService.Find(organisationId);
SessionService.Get.SetUserConversationMessages(organisationMembership);
return SessionService.Get.UnreadMessagesCount.ToString();
}
Here is SessionService
public class SessionService : ISessionService
{
private static SessionService _sessionService;
public static SessionService Get
{
get
{
if (_sessionService == null)
{
_sessionService = new SessionService(new RakletDb());
}
return _sessionService;
}
}
public int UnreadMessagesCount
{
get { return GetSessionInt("UnreadMessagesCount"); }
private set { SetSessionValue("UnreadMessagesCount", value); }
}
private int GetSessionInt(string key)
{
var s = HttpContext.Current.Session[key];
if (s != null)
{
return Int32.Parse(s.ToString());
}
return 0;
}
private void SetSessionValue(string key, object o)
{
HttpContext.Current.Session[key] = o;
}
public void SetUserConversationMessages(OrganisationMembership organisationMembership)
{
this.UnreadMessagesCount = somelinq query;
}
}
My project succesfully compiled. But when i call GetMessageCount method from browser it gives me an error like this:
error
{"Message":"An error has occurred.","ExceptionMessage":"Object reference not set to an instance of an object.","ExceptionType":"System.NullReferenceException","StackTrace":" at Services.SessionService.SetSessionValue(String key, Object o)...
I use something like this in a controller :SessionService.UnreadMessageCount
But in webapi I can only use SessionService.Get.UnreadMessageCount
so what problem can be?

The problem is on HttpContext.Current.Session[key] = o;,Because REST web api by design is stateless. So Default you can't use Session.
I would not suggest you use session on web api.
If you want to use Session,you need to make a HttpHandler which's implement IHttpHandler and let it implement System.Web.SessionState.IRequiresSessionState.
You might be like
public class SessionHander :
System.Web.Http.WebHost.HttpControllerHandler,
System.Web.SessionState.IRequiresSessionState
{
public SessionHander(RouteData routeData) : base(routeData)
{ }
}
public class SeesionRouterHanlder :
System.Web.Http.WebHost.HttpControllerRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new SessionHander(requestContext.RouteData);
}
}
And this code on Global.asax
var route = RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
route.RouteHandler = new SeesionRouterHanlder();

Related

How should I get the Request in ApiController constructor?

Task
I have a DataMapper class that I use to map data into custom representations for my web api's mobile client.
public class DataMapper
{
public static string Role { get; set; }
public static RoleReturnModel Create(IdentityRole appRole)
{
return new RoleReturnModel
{
Id = appRole.Id,
Name = appRole.Name
};
}
public static CountryReturnModel Create(Country country)
{
return new CountryReturnModel
{
Id = country.Id,
Name = country.Name,
CityList = country.Cities.Select(city => DataMapper.Create(city))
};
}
public static CityReturnModel Create(City city)
{
return new CityReturnModel
{
Id = city.Id,
Name = city.Name,
};
}
}
The first property as you can see is called Role. I need to populate that with whichever role is accessing my web method. This is so because at times I need conditional mapping to return role specific data representations to the client.
Problem
I thought the best place to do DataMapper.Role = CurrentRole would be in the constructor of my ApiController
public class BaseApiController : ApiController
{
private ModelFactory _modelFactory;
private ApplicationUserManager _AppUserManager = null;
private ApplicationRoleManager _AppRoleManager = null;
protected BaseApiController()
{
//Request is null here
DataMapper.Role = Request.GetOwinContext().GetUserManager<ApplicationRoleManager>().FindById(User.Identity.GetUserId()).Name;
}
This however doesn't work . The Request object is null in the constructor. It only gets filled in my actual web method
public class UsersController : BaseApiController
{
IUserRepository UserRepository;
public UsersController() // will use ninject for constructor injection
{
UserRepository = new UserRepository();
}
[Route("profile")]
public IHttpActionResult GetUser()
{
//Request is available here
}
I am a webapi noobie. Need pointers to this problem.
The request is not available as yet in the constructor. You can only access it in an action/method after the controller has already been initialized.
public class BaseApiController : ApiController {
private ModelFactory _modelFactory;
private ApplicationUserManager _AppUserManager = null;
private ApplicationRoleManager _AppRoleManager = null;
protected string GetRole() {
return Request.GetOwinContext()
.GetUserManager<ApplicationRoleManager>()
.FindById(User.Identity.GetUserId()).Name;
}
And accessed
public class UsersController : BaseApiController {
IUserRepository UserRepository;
public UsersController() // will use ninject for constructor injection
{
UserRepository = new UserRepository();
}
[Route("profile")]
public IHttpActionResult GetUser()
{
//Request is available here
var role = GetRole();
}
Or consider extracting that out into an extension method so that it can be reused
var role = this.GetUserRole();
Where
public static string GetUserRole(this ApiController controller) {
var request = controller.Request;
var user = controller.User
return request.GetOwinContext()
.GetUserManager<ApplicationRoleManager>()
.FindById(user.Identity.GetUserId()).Name;
}

Call particular filter for particular Razor Pages Route?

I have 2 domains (.com and .ru) and 2 URLs like site.com/about-us and site.ru/o-nas which should be redirected to the same page. The site uses Razor Pages.
Also, the particular URL should be available in the appropriate domain. For example:
site.COM/o-nas should not work and return Not Found (404)
site.RU/about-us should not work and return Not Found (404)
I found that filters work OK, but for both for site.com/about-us and site.ru/o-nas both filters are called.
How to call only 1 for particular URL, is it possible? Thank you, my current code is below.
public static class DomainFilters
{
public static IPageApplicationModelConvention DomainEng(
this PageConventionCollection con, string pageName, string route = "")
{
return con.AddPageApplicationModelConvention(pageName, model =>
{
model.Filters.Add(new EnglishActionFilter(route));
});
}
public static IPageApplicationModelConvention DomainRussian(
this PageConventionCollection con, string pageName, string route = "")
{
return con.AddPageApplicationModelConvention(pageName, model =>
{
model.Filters.Add(new RussianActionFilter(route));
});
}
}
public class EnglishActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.HttpContext.Request.Host.ToString().Contains(".ru"))
{
context.Result = new NotFoundResult();
}
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
public class RussianActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.HttpContext.Request.Host.ToString().Contains(".com"))
{
context.Result = new NotFoundResult();
}
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
And finally ConfigureServices method from Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.DomainEng("/AboutUs", "about-us");
options.Conventions.DomainRussian("/AboutUs", "o-nas");
})
}
Consider implementation of a custom FilterFactory:
public class LanguageFilterFactory : Attribute, IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var context = serviceProvider.GetService<IHttpContextAccessor>();
if (context.HttpContext.Request.Host.ToString().Contains(".com"))
{
return new EnglishActionFilter();
}
return new RussianActionFilter();
}
}
This factory will create either an English or Russian filter (depending on the domain). That's all about its responsibilities. The rest goes to Filters themselves (you'll need to change a code inside the filters to make them validate the page locator):
public class RussianActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
// you may want to play with RouteData in order to make this check more elegant
if (context.HttpContext.Request.Path.Value.Contains("About"))
{
context.Result = new NotFoundResult();
}
}
}
The filter factory is applied in the same way as other filters:
[LanguageFilterFactory]
public class IndexModel : PageModel
The Startup.cs file update:
.AddMvcOptions(options =>
{
options.Filters.Add<LanguageFilterFactory>();
});

DefaultInlineConstraintResolver was unable to resolve CustomRouteConstraint - Web API

This issue was raise twice on SO, I have tried those solutions, but it is not working for me.
I am trying to create CustomRouteConstraint for a ComplexObject which is a parameter in ActionMethod, where HttpPost is used.
I am following this url to implement Custom Routing.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
1. Creating CustomRouteConstraint.
public class CustomRouteConstraint : IHttpRouteConstraint
{
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
object value;
if (values.TryGetValue(parameterName, out value) && value != null)
{
return true;
}
return false;
}
}
2. MapAttribute in WebAPIConfig.cs file
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("CustomObjRouteConstraint", typeof(CustomRouteConstraint));
config.MapHttpAttributeRoutes(constraintResolver);
}
}
3. Using Constraint in ActionMethod.
[Route("Address/{param:CustomObjRouteConstraint}")]
[HttpPost]
public AddressUpdateResult UpdateCustomerAddresses(UpdateCustomerAddressCommand param)
{
Handle(param);
return xyz;
}
At run-time I get error :
"DefaultInlineResolver was unable to resolve CustomRouteConstraint "

My api methods won't run after implementing a new method

public class ContactsController : ApiController
{
static readonly IContactsRepository repository = new ContactsRepository();
//
// GET: /Contacts/
public IEnumerable<Contact> GetAllContacts()
{
return repository.GetAll();
}
}
The above code works fine with the API Call /api/contacts/GetAllContacts and returns a list of contacts from my database. I also want to add an method which returns a specific contact using something like /api/contacts/getcontacts? However, once I add in the following code:
public Contacts GetContact(int id)
{
Contacts item = repository.Get(id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return item;
}
My original call (/api/contacts/GetAllContact) will not work and displays the following error:
"Message": "The request is invalid.",
"MessageDetail": "The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'ReservationsAPI.Models.Contacts GetContact(Int32)' in 'ReservationsAPI.Controllers.ContactsController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."
EDIT:
route config
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Remove ContactS class that you manually created and try this;
public class ContactController : ApiController
{
static readonly IContactsRepository repository = new ContactsRepository();
// GET api/Contact
public IEnumerable<Contact> GetContact()
{
return repository.GetAll();
}
// GET api/Contact/5
public IHttpActionResult GetContact(int id)
{
var contact = repository.Get(id);
if (contact == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return contact;
}
Then try to call these urls;
/api/Contact
/api/Contact/1
With this setup, you don't need to define action in your routing.

how to execute huge rest based url in wcf data services?

I'm using Wcf data service(V3). From IOS App they will send Signature through URL. Problem is sometimes user enters long signature in that situation it is giving an error like "Url is too long". how can i fix this issue on wcf data services.
Advance Thanks.
If the message client want to give to service is large, it is recommended to use POST.
You can find the guide for Actions in WCF Data Service V3 here:
http://blogs.msdn.com/b/odatateam/archive/2011/10/17/actions-in-wcf-data-services.aspx
And here is quick demo for setting up a WCF DS service with Action support:
public class Service : DataService<Context>, IServiceProvider
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceActionAccessRule("*", ServiceActionRights.Invoke);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}
public object GetService(Type serviceType)
{
return typeof(IDataServiceActionProvider) == serviceType ? new ActionProvider() : null;
}
}
public class ActionProvider : IDataServiceActionProvider, IDataServiceActionResolver
{
private static List<ServiceAction> actions;
static ActionProvider()
{
ServiceAction movieRateAction = new ServiceAction(
"Action1", // name of the action
ResourceType.GetPrimitiveResourceType(typeof(string)), // no return type i.e. void
null, // no return type means we don’t need to know the ResourceSet so use null.
OperationParameterBindingKind.Never,
new ServiceActionParameter[] {
new ServiceActionParameter("val", ResourceType.GetPrimitiveResourceType(typeof(string)))
}
);
movieRateAction.SetReadOnly();
actions = new List<ServiceAction>() { movieRateAction };
}
public IEnumerable<ServiceAction> GetServiceActions(DataServiceOperationContext operationContext)
{
return actions;
}
public bool TryResolveServiceAction(DataServiceOperationContext operationContext, string serviceActionName,
out ServiceAction serviceAction)
{
serviceAction = null;
return false;
}
public IEnumerable<ServiceAction> GetServiceActionsByBindingParameterType(DataServiceOperationContext operationContext,
ResourceType bindingParameterType)
{
return Enumerable.Empty<ServiceAction>();
}
public IDataServiceInvokable CreateInvokable(DataServiceOperationContext operationContext, ServiceAction serviceAction,
object[] parameterTokens)
{
return new DataServiceInvokable(parameterTokens);
}
public bool AdvertiseServiceAction(DataServiceOperationContext operationContext, ServiceAction serviceAction, object resourceInstance, bool resourceInstanceInFeed, ref ODataAction actionToSerialize)
{
actionToSerialize = null;
return false;
}
public bool TryResolveServiceAction(DataServiceOperationContext operationContext, ServiceActionResolverArgs resolverArgs, out ServiceAction serviceAction)
{
serviceAction = actions[0];
return true;
}
}
public class DataServiceInvokable : IDataServiceInvokable
{
private readonly object[] parameters;
private string result;
public DataServiceInvokable(object[] parameters)
{
this.parameters = parameters;
}
public object GetResult()
{
return result;
}
public void Invoke()
{
result = parameters[0] as string;
}
}
Then you could send a POST request to http://example.org/service.svc/Action1
Header:
Content-Type: Application/json
Request Body:
{"val":"MessageToPostHere..."}
If you are using .Net 4.0 or above, you could experiment with your web.config settings file, with this:
<system.web>
...
<httpRuntime maxUrlLength="500" />
....
</system.web>

Categories

Resources