How Data is posted (POST) to Service in Servicestack , is it through URL? - asp.net-mvc

I have complex requstDto which composed of other list of DTO's (Entity framework Entities) like
[Route("/demoservice/{Userdemo}/{EmployerDemoid}/{ReportDemo}/{DemoselectedDataList}/", "POST")]
public class ReportDemo : IReturn<String>
{
public List<selectedidList> selectedDataList{ get; set; }
}
where UserReport is follows
public class UserReport
{
public string UserName { get; set; }
public Datetime CreatedON{ get; set; }
}
when i try to post to request it gives me following error
A potentially dangerous Request.Path value was detected from the client (:)
i think it gives error due to : in CreatedON field ( for time part).
is the post values are also sent through URL to ServiceStack URL ? if yes
1) what if we have very large and complex requestDTO resulting into large number of characters (greater than allowed )in URL?
2) how to make above scenario work as ":" is reserved and cant be sent through URL?
3) How to see request URL Generated from client ?
My Client code in MVC.net is
var client = new JsonServiceClient(ConfigurationManager.AppSettings["applicationUrl"])
{
//for windows authentication
Credentials = CredentialCache.DefaultCredentials
};
var result = client.Post (new ReportDemo
{
UserName = model.UserName,
EmployerID = model.EmployerID,
Report = model.Report,
selectedDataList =userReportViewModel.selectedDataList
});
Thanks in advance,
Amol

Only the /path/info of the Url should be specified in the [Route]. Ideally routes should use a human-readable logically-structured Url that refers to a "Resource" (noun). See the SeviceStack REST Events Example for different examples.
Routes should also never include complex types and any variable that isn't on the [Route] is automatically sent in the HTTP Request Body for POST requests or the QueryString from GET Requests.
For a User Report like this I would choose a URL that identifies the report, if the report has a name like "Demo Report" I would use a path info like:
[Route("/reports/demo")]
public class ReportDemo : IReturn<String> { ... }
Otherwise if this is a Report for Users you may instead want to use something like:
[Route("/users/{UserName}/reports/demo")]
public class ReportDemo : IReturn<String> { ... }
You can check what url is used by using the Reverse Routing Extension methods, e.g:
var request = ReportDemo { UserName = "Foo", ... };
request.ToPostUrl().Print(); //= /users/Foo/reports/demo
Now you can send your Request with any property not in the Route getting POST'ed to the above url, e.g:
string result = client.Post (new ReportDemo {
UserName = userReportViewModel.UserName,
EmployerID = userReportViewModel.EmployerID,
Report = userReportViewModel.Report,
selectedDataList =userReportViewModel.selectedDataList
});
If your Report does return a string you can use IReturn<string> however if it returns a Response DTO you'll want to use that instead, e.g IReturn<ReportDemoResponse>.

Related

Manipulate the url using routing

In my website I have the following route defined:
routes.MapRoute(
name: "Specific Product",
url: "product/{id}",
defaults: new { controller = "", action = "Index", id = UrlParameter.Optional }
);
In that way I want customers to be able to add the ID of the product and go to the product page.
SEO advisors have said that it would be better if we could add a description of the product on the URL, like product-name or something. So the URL should look something like:
/product/my-cool-product-name/123
or
/product/my-cool-product-name-123
Of course the description is stored in the db and I cannot do that with a url rewrite (or can I?)
Should I add a redirection on my controller (this would seem to do the job, but it just doesn't feel right)
On a few sites I checked they do respond with a 301 Moved Permanently. Is that really the best approach?
UPDATE
As per Stephen Muecke's comment I checked on what is happening on SO.
The suggested url was my own Manipulate the url using routing and i opened the console to see any redirections. Here is a screenshot:
So, first of all very special thanks to #StephenMuecke for giving the hint for slugs and also the url he suggested.
I would like to post my approach which is a mix of that url and several other articles.
My goal was to be able to have the user enter a url like:
/product/123
and when the page loads to show in the address bar something like:
/product/my-awsome-product-name-123
I checked several web sites that have this behaviour and it seems that a 301 Moved Permanently response is used in all i checked. Even SO as shown in my question uses 301 to add the title of the question. I thought that there would be a different approach that would not need the second round trip....
So the total solution i used in this case was:
I created a SlugRouteHandler class which looks like:
public class SlugRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var url = requestContext.HttpContext.Request.Path.TrimStart('/');
if (!string.IsNullOrEmpty(url))
{
var slug = (string)requestContext.RouteData.Values["slug"];
int id;
//i care to transform only the urls that have a plain product id. If anything else is in the url i do not mind, it looks ok....
if (Int32.TryParse(slug, out id))
{
//get the product from the db to get the description
var product = dc.Products.Where(x => x.ID == id).FirstOrDefault();
//if the product exists then proceed with the transformation.
//if it does not exist then we could addd proper handling for 404 response here.
if (product != null)
{
//get the description of the product
//SEOFriendly is an extension i have to remove special characters, replace spaces with dashes, turn capital case to lower and a whole bunch of transformations the SEO audit has requested
var description = String.Concat(product.name, "-", id).SEOFriendly();
//transform the url
var newUrl = String.Concat("/product/",description);
return new RedirectHandler(newUrl);
}
}
}
return base.GetHttpHandler(requestContext);
}
}
From the above i need to also create a RedirectHandler class to handle the redirections. This is actually a direct copy from here
public class RedirectHandler : IHttpHandler
{
private string newUrl;
public RedirectHandler(string newUrl)
{
this.newUrl = newUrl;
}
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext httpContext)
{
httpContext.Response.Status = "301 Moved Permanently";
httpContext.Response.StatusCode = 301;
httpContext.Response.AppendHeader("Location", newUrl);
return;
}
}
With this 2 classes i can transform product ids to SEO friendly urls.
In order to use these i need to modify my route to use the SlugRouteHandler class, which leads to :
Call SlugRouteHandler class from the route
routes.MapRoute(
name: "Specific Product",
url: "product/{slug}",
defaults: new { controller = "Product", action = "Index" }
).RouteHandler = new SlugRouteHandler();
Here comes the use of the link #StephenMuecke mentioned in his comment.
We need to find a way to map the new SEO friendly url to our actual controller. My controller accepts an integer id but the url will provide a string.
We need to create an Action filter to handle the new param passed before calling the controller
public class SlugToIdAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var slug = filterContext.RouteData.Values["slug"] as string;
if (slug != null)
{
//my transformed url will always end in '-1234' so i split the param on '-' and get the last portion of it. That is my id.
//if an id is not supplied, meaning the param is not ending in a number i will just continue and let something else handle the error
int id;
Int32.TryParse(slug.Split('-').Last(), out id);
if (id != 0)
{
//the controller expects an id and here we will provide it
filterContext.ActionParameters["id"] = id;
}
}
base.OnActionExecuting(filterContext);
}
}
Now what happens is that the controller will be able to accept a non numeric id which ends in a number and provide its view without modifying the content of the controller. We will only need to add the filter attribute on the controller as shown in the next step.
I really do not care if the product name is actually the product name. You could try fetching the following urls:
\product\123
\product\product-name-123
\product\another-product-123
\product\john-doe-123
and you would still get the product with id 123, though the urls are different.
Next step is to let the controller know that it has to use a special filer
[SlugToId]
public ActionResult Index(int id)
{
}

What is the correct REST Urlpattern for POST operation

I have rest url support POST request. its looks like
api/country/{countryId}/state
using to create state resource in a country with given id
but the mapping function of this url is
public HttpResponseMessage Post(int countryId,StateDto state)
{
var country = _countryAppService.AddNewState(state, countryId);
var message = Request.CreateResponse(HttpStatusCode.Created, country);
return message;
}
and expected sample of the given urls is like
api/country/1/state (create a new state in country with id=1)
but here i am not using the url value (1) in the above function instead of here the caller need to pass the corresponding countryId via request body, ie there is no any guarantee to both contryId in url and post request are same. so my doubt is what is the right url pattern to save a state in particular country vai a post request?
If you have the same information in the resource path and the request body, it's duplication of information; the caller should never need to pass you the same information twice in the same request.
You should pick one as the authoritative source and ignore the other. Since you must have the correct resource address to perform the operation, I would suggest you need to take the value from there:
public HttpResponseMessage Post(int countryId,StateDto state)
{
// Compose the DTO from the route parameter.
state.CountryId = countryId;
var country = _countryAppService.AddNewState(state);
var message = Request.CreateResponse(HttpStatusCode.Created, country);
return message;
}
You can pass StateDto object in body also,it will go in body ,id can go in url
public HttpResponseMessage Post([FromUri]int countryId,[FromBody]StateDto state)
{
var country = _countryAppService.AddNewState(state, countryId);
var message = Request.CreateResponse(HttpStatusCode.Created, country);
return message;
}
Only one param can come from body , other have to come from uri, you can read more here:
http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
Passing stateDto in uri is also an option but then you will have to pass all its members in querystring.

Displaying the fullname (firstname, lastname) of the logged in user

I work on an ASP.NET MVC4 solution. When the user is logged in, I would like to display his fullname (not the username provided in the login form). His fullname (firstname + lastname actually stored in the user table in my database) should be displayed in the top right corner.
For better performance, I don't want to query the database each time a request is done.
How to proceed?
Keeping the user information (firstname, lastname, ...) in a cookie?
Keeping the user information is a session variable for all the lifecycle of the application?
Keeping the user information in a 'Profile' like explained here: How to assign Profile values? (*)
Something else?
(*) I think this solution a little complex for the use I have.
Thanks.
I would use a cookie. It doesn't hog up any memory on your machine like Session, and it doesn't hit the database like Profile would. Just remember to delete the cookie when the user signs off.
Note that the Profile would hit the database server each time you make a request. As far as I know, Profile data is not cached anywhere on the web server (unless you have a custom profile provider).
Another reason why I like cookie: if you ever want to store any additional user information for fast access, like UserPrimaryKey, or any special user preferences, you can just store them as JSON in the cookie. Here is an example:
Another note: the code below uses Newtonsoft.Json (the JsonConvert lines). It should come out of the box in an MVC4 project, but for an MVC3 project, you can just add it via nuget.
public class UserCacheModel
{
public string FullName { get; set; }
public string Preference1 { get; set; }
public int Preference2 { get; set; }
public bool PreferenceN { get; set; }
}
public static class UserCacheExtensions
{
private const string CookieName = "UserCache";
// put the info in a cookie
public static void UserCache(this HttpResponseBase response, UserCacheModel info)
{
// serialize model to json
var json = JsonConvert.SerializeObject(info);
// create a cookie
var cookie = new HttpCookie(CookieName, json)
{
// I **think** if you omit this property, it will tell the browser
// to delete the cookie when the user closes the browser window
Expires = DateTime.UtcNow.AddDays(60),
};
// write the cookie
response.SetCookie(cookie);
}
// get the info from cookie
public static UserCacheModel UserCache(this HttpRequestBase request)
{
// default user cache is empty
var json = "{}";
// try to get user cache json from cookie
var cookie = request.Cookies.Get(CookieName);
if (cookie != null)
json = cookie.Value ?? json;
// deserialize & return the user cache info from json
var userCache = JsonConvert.DeserializeObject<UserCacheModel>(json);
return userCache;
}
}
With this, you can read / write the cookie info from a controller like this:
// set the info
public ActionResult MyAction()
{
var fullName = MethodToGetFullName();
var userCache = new UserCache { FullName = fullName };
Response.UserCache(userCache);
return Redirect... // you must redirect to set the cookie
}
// get the info
public ActionResult MyOtherAction()
{
var userCache = Request.UserCache();
ViewBag.FullName = userCache.FullName;
return View();
}

Returning an attachment from a remote web service

Summary
I need to retrieve attachments stored in a parent app from a link in a client of a child app. The attachments are available in the parent app via a web service call -- which returns a standard FileContentResult with content type "application/octet-stream". The best way I can think is to retrieve this via a WebRequest and pass the resulting response stream to a FileStreamResult, though I have some alternatives available.
Does anyone know if, when making a WebRequest, the response stream becomes available immediately once the first part of the response is returned or is it buffered so I don't get the response until all data has been retrieved?
Are there any other options than those listed in the full question below for doing this that I'm missing? (Other than keeping the attachments in both child and parent DBs -- I really don't want to do this since then I'd need to regularly synchronize them, too).
TLDR Version
I have two related applications which communicate through a RESTful web service. The parent application maintains a collection of entities which may have attachments. For example, a Request might have an Excel spreadsheet as an attachment. The entity and its attachment are stored in the database and access to the attachment is controlled using the same logic as access to the Request. That is, you should not be able to download an attachment if you cannot view the Request.
In the child application I maintain some integration glue for the entities assigned to a particular institution -- the app is used to communicate between our Board of Regents and each Regents school. I don't want to maintain and synchronize the full entity/attachment. I only want to maintain enough information to allow me to connect to the web service in the parent app and get the details for entities that the particular instance of the child application has access to.
This works well for the entity data itself. The amount of data is small and the overhead of buffering in the child application doesn't present a signficant delay in accessing the data. If necessary, I could cache the data locally to avoid performance penalities.
My concern is the attachments. I've considered three different mechanisms for providing access to the attachment from a client of the child application.
Generate a one-time use token and associated url that allows the client to directly download the attachment from the parent application. The token generation web service call would ensure that users of the child application should have access to the attachment. The drawback to this is that you'd only be able to click on the link once in the client. Clicking again would result in an error rather than getting the attachment.
Buffer the attachment in the child app. In this scenario I would provide a controller/action to download the attachment in the child app, then call a web service method to get the attachment and have the child app send the attachment as a FileContentResult. This removes the issue of only being able to click the link once, but the attachments could be reasonably large and buffering the data in the child application could potentially double the amount of time to download the attachment and, worse, incur a significant delay before the attachment download begins.
Link in the child app, but provide the stream from the web service request directly to a FileStreamResult. This seems, to me, to be the best option as the FileStreamResult reads in chunks rather than having to have all the data available before it is sent to the client. The only drawback that I can see here is that I can no longer dispose of the WebResponse directly as the FileStreamResult won't be executed until after my action returns.
Here is what I have for the code for API wrapper code for (2) and (3):
private class ResponseModel<T> : IDisposable
{
public T Model { get; set; }
public WebResponse Response { get; set; }
private bool Disposed { get; set; }
private void Dispose( bool disposing )
{
if (!Disposed)
{
if (disposing)
{
((IDisposable)this.Response).Dispose();
}
Disposed = true;
}
}
public void Dispose()
{
Dispose( true );
}
}
private ResponseModel<T> GetAttachmentResponse<T>( long id ) where T : IDownloadModel, new()
{
var request = GetRequest( string.Format( "{0}/api/getattachment/{1}/{2}", this.BaseUrl, this.Key, id ) );
var response = request.GetResponse();
var model = (T)Activator.CreateInstance<T>();
var contentDisposition = response.Headers["Content-Disposition"];
if (!string.IsNullOrEmpty( contentDisposition ))
{
var filename = contentDisposition.Split( new[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries )
.SingleOrDefault( s => s.StartsWith( "filename", StringComparison.OrdinalIgnoreCase ) );
if (!string.IsNullOrEmpty( filename ))
{
model.Name = filename.Split( '=' ).Skip( 1 ).FirstOrDefault();
}
}
if (string.IsNullOrEmpty( model.Name ))
{
model.Name = "untitled";
}
return new ResponseModel<T> { Model = model, Response = response };
}
public FileDownloadModel GetAttachment( long id )
{
using (var response = GetAttachmentResponse<FileDownloadModel>( id ))
{
var reader = new BinaryReader( response.Response.GetResponseStream() );
response.Model.Content = reader.ReadBytes( (int)response.Response.ContentLength );
return response.Model;
}
}
public FileStreamDownloadModel GetAttachmentStream( long id )
{
// since we're returning the stream, we can't dispose of the response when done.
var response = GetAttachmentResponse<FileStreamDownloadModel>( id );
response.Model.Stream = response.Response.GetResponseStream();
return response.Model;
}
public interface IDownloadModel
{
string ContentType { get; }
string Name { get; set; }
}
Model classes
public class FileDownloadModel : IDownloadModel
{
public byte[] Content { get; set; }
public string Name { get; set; }
public string ContentType { get { return "application/octet-stream"; } }
}
public class FileStreamDownloadModel : IDownloadModel
{
public Stream Stream { get; set; }
public string Name { get; set; }
public string ContentType { get { return "application/octet-stream"; } }
}
I would suggest a variant on Option 1 [call it Option 1(a)].
Instead of generating a one-time token, "borrow" the MVC AntiForgeryToken classes, and have your parent application return a custom token and cookie to the child app for inclusion in the form returned to the user.
If the child application may have links for multiple documents on a single page, in the request for the token information, have the child app submit a unique identifier (identifying the page request from the user) as part of the request. You can then use this identifier in generating the tokens, and you can store the identifier as part of the verification process. This will give you a multi-use token, unique for each link on the page.
Slap an expiration time on the unique identifier, and you should be good to go.

How do I specify multiple keys in DataServiceKey attribute for WCF Data Service data context object

I am using Reflection provider for my WCF Data Service and my Data Context object has two key members, say EmpId and DeptId.
If I specify [DataServiceKey("EmpId", "DeptId")], the service doesn't work. When I try to access the collection with the URL http://localhost:55389/DataService.svc/EmployeeData, I get the following error:
The XML page cannot be displayed
Cannot view XML input using XSL style
sheet. Please correct the error and
then click the Refresh button, or try
again later. The following tags were
not closed: feed. Error processing
resource
'http://localhost:55389/DataService.svc/EmployeeData'.
With single member in the DataServiceKey, it works fine. I tried with Custom Data Provider and I could achieve this functionality. But if I can do it with the Reflection provider, that would be great.
I don't think the problem is the multiple keys. To confirm please use for example Fiddler or something similar to grab the whole response from the server and share the error in it (as I'm sure there will be one in there).
Guessing from the description I think the problem is that one of your key property values is null. That is not supported and would cause so called in-stream error which would leave the response XML incomplete (which seems to be your case).
OData can handle multiple keys but all keys must have a valid value. Review this for OData's rule. If you want to retrieve an entry with EmpId=1 and DeptId=someString, you should reconstruct your URI into something like:
http://localhost:55389/DataService.svc/EmployeeData(EmpId=1,DeptId='someString')
Be careful in OData queries because they are case sensitive.
That is weird, I just tried this:
public class Context
{
public IQueryable<Person> People {
get {
return (new List<Person> {
new Person { EmpId = 1, DeptId = 2, Name = "Dude" }
}).AsQueryable();
}
}
}
[DataServiceKey("EmpId", "DeptId")]
public class Person
{
public int EmpId { get; set; }
public int DeptId { get; set; }
public string Name { get; set; }
}
public class WcfDataService1 : DataService<Context>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion =
DataServiceProtocolVersion.V2;
}
}
And it works just fine, do you notice any major differences?
-Alex

Resources